{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "4f698148-a5b5-4f81-9a79-e2444fa00c21",
   "metadata": {},
   "source": [
    "# 本节大纲\n",
    "\n",
    "1. 版本介绍，15分钟\n",
    "2. 嵌入模型，30分钟\n",
    "3. 向量数据库，30分钟\n",
    "4. 大语言模型，30分钟\n",
    "5. 答疑和总结，15分钟"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "382c7d57-33a4-4dfd-a884-5c541ccd0f73",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: sentence-transformers in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (2.2.2)\n",
      "Requirement already satisfied: pymilvus in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (2.3.0)\n",
      "Requirement already satisfied: transformers<5.0.0,>=4.6.0 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from sentence-transformers) (4.33.2)\n",
      "Requirement already satisfied: tqdm in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from sentence-transformers) (4.66.1)\n",
      "Requirement already satisfied: torch>=1.6.0 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from sentence-transformers) (2.0.1)\n",
      "Requirement already satisfied: torchvision in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from sentence-transformers) (0.15.2)\n",
      "Requirement already satisfied: numpy in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from sentence-transformers) (1.26.0)\n",
      "Requirement already satisfied: scikit-learn in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from sentence-transformers) (1.3.0)\n",
      "Requirement already satisfied: scipy in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from sentence-transformers) (1.11.2)\n",
      "Requirement already satisfied: nltk in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from sentence-transformers) (3.8.1)\n",
      "Requirement already satisfied: sentencepiece in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from sentence-transformers) (0.1.99)\n",
      "Requirement already satisfied: huggingface-hub>=0.4.0 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from sentence-transformers) (0.17.2)\n",
      "Requirement already satisfied: grpcio<=1.56.0,>=1.49.1 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from pymilvus) (1.56.0)\n",
      "Requirement already satisfied: protobuf>=3.20.0 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from pymilvus) (4.24.3)\n",
      "Requirement already satisfied: environs<=9.5.0 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from pymilvus) (9.5.0)\n",
      "Requirement already satisfied: ujson>=2.0.0 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from pymilvus) (5.8.0)\n",
      "Requirement already satisfied: pandas>=1.2.4 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from pymilvus) (2.1.0)\n",
      "Requirement already satisfied: marshmallow>=3.0.0 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from environs<=9.5.0->pymilvus) (3.20.1)\n",
      "Requirement already satisfied: python-dotenv in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from environs<=9.5.0->pymilvus) (1.0.0)\n",
      "Requirement already satisfied: filelock in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from huggingface-hub>=0.4.0->sentence-transformers) (3.12.4)\n",
      "Requirement already satisfied: fsspec in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from huggingface-hub>=0.4.0->sentence-transformers) (2023.6.0)\n",
      "Requirement already satisfied: requests in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from huggingface-hub>=0.4.0->sentence-transformers) (2.31.0)\n",
      "Requirement already satisfied: pyyaml>=5.1 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from huggingface-hub>=0.4.0->sentence-transformers) (6.0.1)\n",
      "Requirement already satisfied: typing-extensions>=3.7.4.3 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from huggingface-hub>=0.4.0->sentence-transformers) (4.8.0)\n",
      "Requirement already satisfied: packaging>=20.9 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from huggingface-hub>=0.4.0->sentence-transformers) (23.1)\n",
      "Requirement already satisfied: python-dateutil>=2.8.2 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from pandas>=1.2.4->pymilvus) (2.8.2)\n",
      "Requirement already satisfied: pytz>=2020.1 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from pandas>=1.2.4->pymilvus) (2023.3.post1)\n",
      "Requirement already satisfied: tzdata>=2022.1 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from pandas>=1.2.4->pymilvus) (2023.3)\n",
      "Requirement already satisfied: sympy in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from torch>=1.6.0->sentence-transformers) (1.12)\n",
      "Requirement already satisfied: networkx in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from torch>=1.6.0->sentence-transformers) (3.1)\n",
      "Requirement already satisfied: jinja2 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from torch>=1.6.0->sentence-transformers) (3.1.2)\n",
      "Requirement already satisfied: nvidia-cuda-nvrtc-cu11==11.7.99 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from torch>=1.6.0->sentence-transformers) (11.7.99)\n",
      "Requirement already satisfied: nvidia-cuda-runtime-cu11==11.7.99 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from torch>=1.6.0->sentence-transformers) (11.7.99)\n",
      "Requirement already satisfied: nvidia-cuda-cupti-cu11==11.7.101 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from torch>=1.6.0->sentence-transformers) (11.7.101)\n",
      "Requirement already satisfied: nvidia-cudnn-cu11==8.5.0.96 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from torch>=1.6.0->sentence-transformers) (8.5.0.96)\n",
      "Requirement already satisfied: nvidia-cublas-cu11==11.10.3.66 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from torch>=1.6.0->sentence-transformers) (11.10.3.66)\n",
      "Requirement already satisfied: nvidia-cufft-cu11==10.9.0.58 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from torch>=1.6.0->sentence-transformers) (10.9.0.58)\n",
      "Requirement already satisfied: nvidia-curand-cu11==10.2.10.91 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from torch>=1.6.0->sentence-transformers) (10.2.10.91)\n",
      "Requirement already satisfied: nvidia-cusolver-cu11==11.4.0.1 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from torch>=1.6.0->sentence-transformers) (11.4.0.1)\n",
      "Requirement already satisfied: nvidia-cusparse-cu11==11.7.4.91 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from torch>=1.6.0->sentence-transformers) (11.7.4.91)\n",
      "Requirement already satisfied: nvidia-nccl-cu11==2.14.3 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from torch>=1.6.0->sentence-transformers) (2.14.3)\n",
      "Requirement already satisfied: nvidia-nvtx-cu11==11.7.91 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from torch>=1.6.0->sentence-transformers) (11.7.91)\n",
      "Requirement already satisfied: triton==2.0.0 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from torch>=1.6.0->sentence-transformers) (2.0.0)\n",
      "Requirement already satisfied: setuptools in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from nvidia-cublas-cu11==11.10.3.66->torch>=1.6.0->sentence-transformers) (68.0.0)\n",
      "Requirement already satisfied: wheel in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from nvidia-cublas-cu11==11.10.3.66->torch>=1.6.0->sentence-transformers) (0.38.4)\n",
      "Requirement already satisfied: cmake in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from triton==2.0.0->torch>=1.6.0->sentence-transformers) (3.27.5)\n",
      "Requirement already satisfied: lit in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from triton==2.0.0->torch>=1.6.0->sentence-transformers) (16.0.6)\n",
      "Requirement already satisfied: regex!=2019.12.17 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from transformers<5.0.0,>=4.6.0->sentence-transformers) (2023.8.8)\n",
      "Requirement already satisfied: tokenizers!=0.11.3,<0.14,>=0.11.1 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from transformers<5.0.0,>=4.6.0->sentence-transformers) (0.13.3)\n",
      "Requirement already satisfied: safetensors>=0.3.1 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from transformers<5.0.0,>=4.6.0->sentence-transformers) (0.3.3)\n",
      "Requirement already satisfied: click in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from nltk->sentence-transformers) (8.1.7)\n",
      "Requirement already satisfied: joblib in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from nltk->sentence-transformers) (1.3.2)\n",
      "Requirement already satisfied: threadpoolctl>=2.0.0 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from scikit-learn->sentence-transformers) (3.2.0)\n",
      "Requirement already satisfied: pillow!=8.3.*,>=5.3.0 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from torchvision->sentence-transformers) (10.0.1)\n",
      "Requirement already satisfied: six>=1.5 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from python-dateutil>=2.8.2->pandas>=1.2.4->pymilvus) (1.16.0)\n",
      "Requirement already satisfied: MarkupSafe>=2.0 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from jinja2->torch>=1.6.0->sentence-transformers) (2.1.3)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from requests->huggingface-hub>=0.4.0->sentence-transformers) (3.2.0)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from requests->huggingface-hub>=0.4.0->sentence-transformers) (3.4)\n",
      "Requirement already satisfied: urllib3<3,>=1.21.1 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from requests->huggingface-hub>=0.4.0->sentence-transformers) (2.0.5)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from requests->huggingface-hub>=0.4.0->sentence-transformers) (2023.7.22)\n",
      "Requirement already satisfied: mpmath>=0.19 in /home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages (from sympy->torch>=1.6.0->sentence-transformers) (1.3.0)\n"
     ]
    }
   ],
   "source": [
    "!pip install sentence-transformers pymilvus"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1c68b7f0-65b9-46a4-927c-dda3ce9df25f",
   "metadata": {},
   "source": [
    "### 小墨 v0.3 介绍"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1171e3d4-02a6-4e5f-b8ee-54447aee78d8",
   "metadata": {},
   "source": [
    "![v0.3](./resource/images/v0.3.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b4c29e2-182a-4067-807d-e1e16f11fe72",
   "metadata": {},
   "source": [
    "### 核心就在于三驾马车\n",
    "1. 嵌入模型私有化\n",
    "2. 向量数据库私有化\n",
    "3. 大语言模型私有化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6cd04f6f-64d2-4d6c-9520-54e2755d490e",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "129b3c16-ac98-423c-93f6-57afbf090b8d",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "26e419cf-998f-42b5-a520-fb4816050728",
   "metadata": {},
   "source": [
    "### 私有化，肯定要涉及到基础设施的部署"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eff06aa0-6ace-460e-b61b-21165a6029c7",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "通常是五部曲\n",
    "1. 购买硬件/云服务器（Dell、IBM、Cloud..）\n",
    "3. 安装操作系统（Ubuntu、CentOS..）\n",
    "4. 打上显卡驱动（NVIDIA Drive）\n",
    "5. 选择算法框架及计算架构 （PyTorch-Meta, TensorFlow-Google, MindSpore-Huawei...）（CUDA）\n",
    "6. 运行对应的模型（Embedding、Language...）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8107aaea-8139-4809-9d5c-0734112074f2",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "89a13cc4-e666-42a6-ac20-256db36f9c22",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "27b71d56-1102-4631-828c-0c81ecf874b5",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "106b9c60-ffeb-4221-a78f-591e68d89760",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "11489cbe-f922-498f-9a75-e713aa0b693c",
   "metadata": {},
   "source": [
    "### 魔法，还是得通过魔法打败"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dde2d6f5-7662-4357-9829-b2e11b57be30",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 问题：无法下载到模型权重\n",
    "\n",
    "# 方式一： HuggingFace Mirror\n",
    "# https://aliendao.cn/\n",
    "\n",
    "# 方式二：国内平替，例如阿里云摩搭\n",
    "# https://www.modelscope.cn/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c79a0233-e55a-4db4-83b0-2709bc6613db",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 问题：没有本地算力资源\n",
    "\n",
    "# 方式一：用第三方租用，例如 AWS、腾讯云的 GPU 实例\n",
    "\n",
    "# 方式二：第三方托管（开源模型），例如阿里云灵积直接调用 API（某种意义上也可以做到可控）\n",
    "# https://dashscope.console.aliyun.com/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "39c052a5-609b-4f2f-aeba-adeb62ef2562",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "aeeccd67-17b5-40c6-8ed6-c6dc8e97ea6c",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c56922f2-567e-46c3-9a58-c5313547c52c",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "a8100f63-0787-4c5b-a1ad-19d999694477",
   "metadata": {},
   "source": [
    "### 第一架马车：Embedding Model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "99559224-2cd7-4f9e-b655-8153f5d35617",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 开源模型有很多，也可以自己来根据业务训练（例如专注于零售、医疗、供应链领域..）\n",
    "# 选择一个性能不错的，可以开箱即用的 (Moka Massive Mixed Embedding)\n",
    "# https://huggingface.co/moka-ai/m3e-large"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "2bd5dd5d-4ee0-438b-87e7-337557aad8c8",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "No sentence-transformers model found with name ./weights/embedding/m3e-large/. Creating a new one with MEAN pooling.\n"
     ]
    }
   ],
   "source": [
    "# 很简单的工作过程，输入n个句子，给回n个d维度的向量\n",
    "\n",
    "from sentence_transformers import SentenceTransformer\n",
    "embed_model = SentenceTransformer(model_name_or_path=\"./weights/embedding/m3e-large/\")\n",
    "\n",
    "sentence = ['How are you', \"你好\", \"Bonjour\"]\n",
    "\n",
    "vectors = embed_model.encode(sentence)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "9f4a0921-090b-41a5-bf35-4daacc17c8b1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "提供有 3 个向量，每个向量有 1024 维度\n"
     ]
    }
   ],
   "source": [
    "print(f'提供有 {len(vectors)} 个向量，每个向量有 {len(vectors[0])} 维度')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "0735a25b-22b0-4675-890d-571e3c008c64",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(3, 1024)"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vectors.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "4f666e27-d9da-4d8a-8933-0043bd6fdd14",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 5.9009928e-02, -9.8295927e-02,  5.1568592e-01, ...,\n",
       "         9.7195542e-01, -1.5779861e+00,  1.0853946e-03],\n",
       "       [-3.7582135e-01,  8.0404276e-01, -1.3980484e-01, ...,\n",
       "         6.3225293e-01, -9.5478934e-01,  3.5053524e-01],\n",
       "       [-6.9274282e-01,  7.6224732e-01, -4.0011904e-03, ...,\n",
       "         1.5449513e-01, -1.7018709e+00, -4.8794255e-01]], dtype=float32)"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vectors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "9e33de92-94b1-4db4-a174-e09e77f11560",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(512,)"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 不同的模型，有不同的维度，维度越多，能表达的信息量越多\n",
    "# 同一个模型，也有不同的尺寸，例如 m3e 中的 small, base, large \n",
    "\n",
    "from sentence_transformers import SentenceTransformer\n",
    "m3e_small = SentenceTransformer(model_name_or_path=\"./weights/embedding/m3e-small/\")\n",
    "vectors = m3e_small.encode(\"Hello\")\n",
    "vectors.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "8f72dc1d-fb69-405a-a7d2-bfbafa2ef2ae",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
      "To disable this warning, you can either:\n",
      "\t- Avoid using `tokenizers` before the fork if possible\n",
      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
      "Fri Oct  6 23:49:18 2023       \n",
      "+---------------------------------------------------------------------------------------+\n",
      "| NVIDIA-SMI 535.98                 Driver Version: 535.98       CUDA Version: 12.2     |\n",
      "|-----------------------------------------+----------------------+----------------------+\n",
      "| GPU  Name                 Persistence-M | Bus-Id        Disp.A | Volatile Uncorr. ECC |\n",
      "| Fan  Temp   Perf          Pwr:Usage/Cap |         Memory-Usage | GPU-Util  Compute M. |\n",
      "|                                         |                      |               MIG M. |\n",
      "|=========================================+======================+======================|\n",
      "|   0  NVIDIA GeForce RTX 4090 ...    Off | 00000000:01:00.0 Off |                  N/A |\n",
      "| N/A   48C    P0              16W / 150W |    667MiB / 16376MiB |      0%      Default |\n",
      "|                                         |                      |                  N/A |\n",
      "+-----------------------------------------+----------------------+----------------------+\n",
      "                                                                                         \n",
      "+---------------------------------------------------------------------------------------+\n",
      "| Processes:                                                                            |\n",
      "|  GPU   GI   CI        PID   Type   Process name                            GPU Memory |\n",
      "|        ID   ID                                                             Usage      |\n",
      "|=======================================================================================|\n",
      "|    0   N/A  N/A      2210      G   /usr/lib/xorg/Xorg                            4MiB |\n",
      "|    0   N/A  N/A      2512    C+G   ...libexec/gnome-remote-desktop-daemon      239MiB |\n",
      "|    0   N/A  N/A    701177      C   .../ml/anaconda3/envs/mobot/bin/python      406MiB |\n",
      "+---------------------------------------------------------------------------------------+\n"
     ]
    }
   ],
   "source": [
    "!nvidia-smi"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "040541fc-7dea-42de-ada0-05b9ec97c83a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(768,)"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from sentence_transformers import SentenceTransformer\n",
    "m3e_base = SentenceTransformer(model_name_or_path=\"./weights/embedding/m3e-base/\")\n",
    "vectors = m3e_base.encode(\"Hello\")\n",
    "vectors.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "9e23222a-2e3f-4a4d-9a56-1d63463d9548",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
      "To disable this warning, you can either:\n",
      "\t- Avoid using `tokenizers` before the fork if possible\n",
      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
      "Fri Oct  6 23:49:38 2023       \n",
      "+---------------------------------------------------------------------------------------+\n",
      "| NVIDIA-SMI 535.98                 Driver Version: 535.98       CUDA Version: 12.2     |\n",
      "|-----------------------------------------+----------------------+----------------------+\n",
      "| GPU  Name                 Persistence-M | Bus-Id        Disp.A | Volatile Uncorr. ECC |\n",
      "| Fan  Temp   Perf          Pwr:Usage/Cap |         Memory-Usage | GPU-Util  Compute M. |\n",
      "|                                         |                      |               MIG M. |\n",
      "|=========================================+======================+======================|\n",
      "|   0  NVIDIA GeForce RTX 4090 ...    Off | 00000000:01:00.0 Off |                  N/A |\n",
      "| N/A   48C    P0              20W / 150W |    989MiB / 16376MiB |      1%      Default |\n",
      "|                                         |                      |                  N/A |\n",
      "+-----------------------------------------+----------------------+----------------------+\n",
      "                                                                                         \n",
      "+---------------------------------------------------------------------------------------+\n",
      "| Processes:                                                                            |\n",
      "|  GPU   GI   CI        PID   Type   Process name                            GPU Memory |\n",
      "|        ID   ID                                                             Usage      |\n",
      "|=======================================================================================|\n",
      "|    0   N/A  N/A      2210      G   /usr/lib/xorg/Xorg                            4MiB |\n",
      "|    0   N/A  N/A      2512    C+G   ...libexec/gnome-remote-desktop-daemon      239MiB |\n",
      "|    0   N/A  N/A    701389      C   .../ml/anaconda3/envs/mobot/bin/python      728MiB |\n",
      "+---------------------------------------------------------------------------------------+\n"
     ]
    }
   ],
   "source": [
    "!nvidia-smi"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "00e3bcf0-bdba-4142-9df6-5a821d558b2d",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "No sentence-transformers model found with name ./weights/embedding/m3e-large/. Creating a new one with MEAN pooling.\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "(1024,)"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from sentence_transformers import SentenceTransformer\n",
    "m3e_large = SentenceTransformer(model_name_or_path=\"./weights/embedding/m3e-large/\")\n",
    "vectors = m3e_large.encode(\"Hello\")\n",
    "vectors.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "209dd00f-f823-4ba5-bb50-9859714d04ed",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
      "To disable this warning, you can either:\n",
      "\t- Avoid using `tokenizers` before the fork if possible\n",
      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
      "Fri Oct  6 23:50:00 2023       \n",
      "+---------------------------------------------------------------------------------------+\n",
      "| NVIDIA-SMI 535.98                 Driver Version: 535.98       CUDA Version: 12.2     |\n",
      "|-----------------------------------------+----------------------+----------------------+\n",
      "| GPU  Name                 Persistence-M | Bus-Id        Disp.A | Volatile Uncorr. ECC |\n",
      "| Fan  Temp   Perf          Pwr:Usage/Cap |         Memory-Usage | GPU-Util  Compute M. |\n",
      "|                                         |                      |               MIG M. |\n",
      "|=========================================+======================+======================|\n",
      "|   0  NVIDIA GeForce RTX 4090 ...    Off | 00000000:01:00.0 Off |                  N/A |\n",
      "| N/A   48C    P0              25W / 150W |   1821MiB / 16376MiB |      2%      Default |\n",
      "|                                         |                      |                  N/A |\n",
      "+-----------------------------------------+----------------------+----------------------+\n",
      "                                                                                         \n",
      "+---------------------------------------------------------------------------------------+\n",
      "| Processes:                                                                            |\n",
      "|  GPU   GI   CI        PID   Type   Process name                            GPU Memory |\n",
      "|        ID   ID                                                             Usage      |\n",
      "|=======================================================================================|\n",
      "|    0   N/A  N/A      2210      G   /usr/lib/xorg/Xorg                            4MiB |\n",
      "|    0   N/A  N/A      2512    C+G   ...libexec/gnome-remote-desktop-daemon      239MiB |\n",
      "|    0   N/A  N/A    701594      C   .../ml/anaconda3/envs/mobot/bin/python     1560MiB |\n",
      "+---------------------------------------------------------------------------------------+\n"
     ]
    }
   ],
   "source": [
    "!nvidia-smi"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b0b851c1-5172-4857-a6d2-eff60262a04b",
   "metadata": {},
   "source": [
    "#### 关键：如何评估模型的效果？"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a2fbb3c7-a6d9-4cba-9aec-1bafc63eaa2e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在一个多维空间里面，距离可以来评估相似度\n",
    "# 那么目标函数可以定义为，语义越相似的句子距离越近，越不相似的句子距离越远"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "989f2956-830b-4cc8-8007-9061b31cef50",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "No sentence-transformers model found with name ./weights/embedding/m3e-large/. Creating a new one with MEAN pooling.\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "from sentence_transformers import SentenceTransformer\n",
    "embed_model = SentenceTransformer(model_name_or_path=\"./weights/embedding/m3e-large/\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "d75c3c77-933b-4000-8241-7bd19f18e8e5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0.      , 17.78149 , 30.888874], dtype=float32)"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 语义都是打招呼，但是不同语言表达\n",
    "\n",
    "# 原始文本\n",
    "sentences = ['你好', \"Hello\", \"Benjour\"]\n",
    "\n",
    "# 向量化\n",
    "vectors = embed_model.encode(sentences)\n",
    "\n",
    "# 相似度计算\n",
    "l2_distances = np.linalg.norm((vectors[0] - vectors), axis=1)\n",
    "l2_distances"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "f53a1c04-973a-42fb-b169-b2a62d06d6c1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0.      , 31.430096, 32.619556, 33.663322], dtype=float32)"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 语义不一样时\n",
    "\n",
    "# 原始文本\n",
    "sentences = [\"瑶瑶是墨问西东颜值最高的女孩子\", \"你好\", \"Hello\", \"Benjour\"]\n",
    "\n",
    "# 向量化\n",
    "vectors = embed_model.encode(sentences)\n",
    "\n",
    "# 相似度计算\n",
    "l2_distances = np.linalg.norm((vectors[0] - vectors), axis=1)\n",
    "l2_distances"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "49f38014-b52a-4932-a875-03cbcc066f43",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0.      , 22.221102, 19.63204 ], dtype=float32)"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 均是中文时\n",
    "\n",
    "# 原始文本\n",
    "sentences = [\"早上好\", \"午安\", \"晚安\"]\n",
    "\n",
    "# 向量化\n",
    "vectors = embed_model.encode(sentences)\n",
    "\n",
    "# 相似度计算\n",
    "l2_distances = np.linalg.norm((vectors[0] - vectors), axis=1)\n",
    "l2_distances"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "d34841eb-922c-45c9-80b0-fcb4e8ec9a4c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0.      , 22.924166, 30.93319 , 31.255424], dtype=float32)"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 真实场景\n",
    "\n",
    "# 原始文本\n",
    "sentences = [\"小盖是谁\", \"小盖是墨问西东最帅的男人。\", \"瑶瑶是墨问西东的颜值担当，遥遥领先。\",\n",
    "             \"这个季节就是皮肤的水分流失相当快，要特别注重补水哦！我们新出了一款产品，使用后皮肤水汪汪的，滋润而不油腻。是我们卖的最好的明星产品之一。\"]\n",
    "\n",
    "# 向量化\n",
    "vectors = embed_model.encode(sentences)\n",
    "\n",
    "# 相似度计算\n",
    "l2_distances = np.linalg.norm((vectors[0] - vectors), axis=1)\n",
    "l2_distances"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad5ffda8-522f-4d7a-88e7-88744168f157",
   "metadata": {},
   "source": [
    "#### 技巧"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a5877635-2660-4a96-a4b0-218f97412d81",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 工程挑战，召回大量的知识，但是需要做抑制\n",
    "# 通过比对试验选择一个合适的阈值，\n",
    "# m3e large 这样看起来应该是 <20 (经验值)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2705854b-4c9e-4056-bfd1-442ec791ce19",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "163cf58c-db4f-4107-babd-e4190425736c",
   "metadata": {},
   "source": [
    "#### 可视化向量结果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "d16d8642-3a4e-4539-82bc-eb0c42368f22",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 问题：向量都是一堆数据，我也不知道是什么含义\n",
    "#      而且我最多只能理解一维, 二维, 三维世界，更多维度我也不知道怎么想象出来"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "3ceb3ccb-a55a-4546-a5fc-a917ec98e0ce",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 原始文本\n",
    "sentences = [ \"你好\", \"早上好\", \"中午好\", \"晚上好\",\n",
    "             \"小盖是谁\", \"小盖是墨问西东最帅的男人。\", \n",
    "             \"瑶瑶是墨问西东的颜值担当，遥遥领先。\",\n",
    "             \"这个季节就是皮肤的水分流失相当快，要特别注重补水哦！我们新出了一款产品，使用后皮肤水汪汪的，滋润而不油腻。是我们卖的最好的明星产品之一。\"]\n",
    "\n",
    "# 文本标签 \n",
    "labels = [0, 0, 0, 0, 1, 1, 2, 3] \n",
    "\n",
    "# 向量化\n",
    "vectors = embed_model.encode(sentences)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "652e1275-1fb0-4fc5-9ed6-d4f95362e0bd",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(8, 2)"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 既然只能理解二维，那没有问题，我们把向量降维，不就可以了\n",
    "from sklearn.manifold import TSNE\n",
    "\n",
    "# 创建 t-SNE 模型来处理数据\n",
    "tsne = TSNE(n_components=2, perplexity=2, random_state=42, init='random', learning_rate=200)\n",
    "vis_dims = tsne.fit_transform(vectors)\n",
    "vis_dims.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "a523bf28-d194-4d08-9844-4a533075d0d5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 可视化下二维情况下的向量空间\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "x = [point[0] for point in vis_dims]\n",
    "y = [point[1] for point in vis_dims]\n",
    "\n",
    "plt.scatter(x, y)\n",
    "plt.xlabel('X')\n",
    "plt.ylabel('Y')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "4919420a-97da-42a3-aaf4-729ab92a41d2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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zLZt25zYrBZrzjgEAgGTlpZmJk2aZLZv326efPJJ061bN/Poo4+G+8T6OBhjzMKFC839999v5syZY7xeb5N9JJnVq1dH/D188cUX4fXvv/++SUpKMnPmzDE7d+40Dz/8sImLizMbNmyI0la0jpONRV1dnfnOd75jsrOzzdtvv23WrVtn+vbtaxYsWBDu01HGwnbaaaeZu+++O+L3f/jw4fD65nxWOoK1a9eahIQE89hjj5kdO3aYG2+80SQnJ5vKykqnS2szixYtMmeffXbE7/7AgQPh9TfffLPJyMgwhYWF5s033zQjR440o0aNcrDi1kc46qSWLVtmBg4cGH7cGI7efvvt4z7nhz/8ocnNzY1oy8rKMj/5yU/aqsw299VxWLFihenVq5eprq4Ot82fP98MGjQo/LgjjcPq1atPGI6effbZ4z73tttuM2effXZE28SJE01OTk4rVhg9xxuLdevWGbfbbfx+f7jtN7/5jfF4POG/k442FsaEwtEDDzxw3PXN+ax0BBdeeKHJy8sLP66vrzf9+vUzS5YscbCqtrVo0SIzbNiwJtdVVVWZLl26mKeffjrctmvXLiPJFBcXR6nCtsdhtU4qEAiod+/ex7Rfc801Sk1N1cUXX6y//vWvEeuKi4uVnZ0d0ZaTk6Pi4uI2rbUtfXUciouLdckllyghISHclpOToz179uizzz4L9+lo43A8eXl56tu3ry688EI99thjMtZt0TrLOBQXF2vo0KFKS0sLt+Xk5CgYDGrHjh3hPh1xLJYuXao+ffro3HPP1b333htxKLE5n5VYV1NTo5KSkojfrdvtVnZ2dsz/bk9m79696tevn8444wxNmjRJZWVlkqSSkhLV1tZGjMngwYM1YMCADjUmfPFsJ/Tuu+/q4Ycf1q9//etwW48ePXTffffpoosuktvt1p/+9CeNHTtWf/7zn3XNNddIkvx+f8QOQpLS0tLk9/ujWn9raWoc/H6/Bg4cGNGvcZv9fr969erV4cbheO6++25ddtllSkpK0osvvqhbbrlFhw8f1q233irp+H8PwWBQX3zxhbp16+ZE2a3ueNvZuO5EfWJ5LG699Vadd9556t27t4qKirRgwQJ98sknuv/++yU177MS6z799FPV19c3+bvdvXu3Q1W1vaysLOXn52vQoEH65JNP9Itf/ELf/e53tX37dvn9fiUk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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 但是不知道上面的那一个点是什么意思\n",
    "# 我们有标签值来代表他是什么类别，所以我们可以根据不同的类别来显示不同的颜色\n",
    "# 只要颜色一样的距离是相近的，那也可以表达出语义是相似的\n",
    "\n",
    "color_map = ['r', 'g', 'b', 'y']\n",
    "colors = [color_map[lab] for lab in labels]\n",
    "\n",
    "x = [point[0] for point in vis_dims]\n",
    "y = [point[1] for point in vis_dims]\n",
    "\n",
    "plt.scatter(x, y, color=colors)\n",
    "plt.xlabel('X')\n",
    "plt.ylabel('Y')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "ae151073-3579-44e6-8342-d0ca7f835f0e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(8, 3)"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 既然可以二维，那就继续试试三维\n",
    "from mpl_toolkits.mplot3d import Axes3D\n",
    "from sklearn.decomposition import PCA\n",
    "\n",
    "pca = PCA(n_components=3)\n",
    "data_transformed = pca.fit_transform(vectors)\n",
    "data_transformed.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "4c9a3f2c-ea9d-4b87-b9db-bf77c1fed08a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZYAAAGNCAYAAAAy6VfOAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy81sbWrAAAACXBIWXMAAA9hAAAPYQGoP6dpAACwJElEQVR4nOy9d3wc9Z3//9qi3rvVi1XdVF1kSCihGQNWSLiUC3bKkXxJuwCBkAqEEJKQyyUQQo5cEiDJ7xJiGwOBUAw2EGwglrTqvXetdtW2l5nfH85nmF3trmZ3ZnZG1jwfjzzukO3Z0Wr1ec27vd4qmqZpKCgoKCgoCIRa6htQUFBQULiwUIRFQUFBQUFQFGFRUFBQUBAURVgUFBQUFARFERYFBQUFBUFRhEVBQUFBQVAUYVFQUFBQEBRFWBQUFBQUBEURFgUFBQUFQVGERUFBQUFBUBRhUVBQUFAQFEVYFBQUFBQERREWBQUFBQVBUYRFQUFBQUFQFGFRUFBQUBAURVgUFBQUFARFERYFBQUFBUFRhEVBQUFBQVAUYVFQUFBQEBRFWBQUFBQUBEURFgUFBQUFQVGERUFBQUFBUBRhUVBQUFAQFEVYFBQUFBQERREWBQUFBQVBUYRFQUFBQUFQFGFRUFBQUBAURVgUFBQUFARFERYFBQUFBUFRhEVBQUFBQVAUYVFQUFBQEBRFWBQUFBQUBEURFgUFBQUFQVGERUFBQUFBUBRhUVBQUFAQFEVYFBQUFBQERSv1DShsLmiahtvtht1uh0ajYf6nVivPOAoKFwqKsCiEDZqm4XQ64XK5YLfbAQAqlQoqlQparRZarVYRGgWFCwAVTdO01DehcOHjdrvhdDpBURRUKhUcDgfUajVomgZFUWB/DL2FRqvVQqVSSXj3CgoKwaAIi4Ko0DQNl8sFl8sFmqYZMXE6nT7Fgi00NE1DpVJBrVYzAkPERhEaBQX5ogiLgmhQFAWz2Yyenh7s3LmTSXtRFAWn0wkA6wqEP6HxTp0pQqOgIB+UGouC4BAxcDqdcDgcmJ2dRXV19Zq/w0UMVCoVNBoN82+A84LlcDhgt9sVoVFQkCGKsCgICjv1BYBJfQkBEQtFaBQU5I0iLAqCQaIUt9vNHPCku4trhBIMgYTGbrfD4XAAgCI0CgphRhEWBd6Q2RSXywWKoqBWq5mDm/xfMYTFG7bQaDQapi5D0/QaoYmIiGAaAtj3q6CgwB9FWBR4QTq83G43AKw5pNnCEm5IswCANULT19cHACgqKmKEhkQ0itAoKPBDERaFkCG1De8ohY0vYZHq0GYLDREYUgOy2WzM31GERkGBH4qwKAQNSX05nU7mcPZ38PqLWORyUJMakHdEwxYa7xkaRWgUFAKjCItCUKyX+vJGylRYsPhLnVEUxQgNaUhQhEZBwT+KsChwhh2lsA/hQGwkYfHGn9C43W7GSNNXe7MiNAqbHUVYFNbFezaFq6h4X0NOqFSqoO+JfN/sFmp/QsPuOgvl/VJQ2MgowqIQEDKbQlEUAATtOryRI5b1CCQ0LpeL+XNfPmeK0ChcyCjCouATti1LoK6v9biQhcUbf0LjcrkY00273Q6appGSkqKsCFC4YFGERWENNE1jeXkZLpcLcXFxvGsGcn06F1vsfAmNXq/H6uoqoqKiPCIakjpThEbhQkARFgUPSJQyMTEBu92OnTt38r5mKPWMCxEisKQG4x3RkD9Tlp4pbHQUYVEA4NuWRUjzSEVY1uIvdUZcoZXtmgobFUVYFHzOpqjVaqZgzxdFWDwJNEzqLTQkgmTvr1G2ayrIHUVYNjnslcHe5pEXcsQi1WEczPvA3kVD/i17z413RKNs11SQC4qwbFJ87U1hH0hip8LkKDbhItSDn4vQkDXO7GYARWgUwo0iLJsQ79kUX3MVYkcschAVKe5ByNfkKjTKLhqFcKMIyyaCffCsZ8tCdtMLAVtYaJrG+Pg4+vr6EBMTg9TUVKSkpCA5ORlarfJx5MN6a5wnJiaQnZ2NuLg4RWgUREX5Td4keBfo15v+FiMV5nQ60dXVhcXFRezYsQNutxtLS0sYGBiAzWZDQkICUlJSkJKSgqSkJI+n8QuJcBzkvrZrTk1NIS0tDREREcoaZwVRUYRlE8BeGcx12FHoVJjJZEJHRwfi4uKwf/9+5vpZWVkAAJvNhsXFRSwuLqKnpwdOpxOJiYlMRJOQkHBBtNlKlQIk7zfpJAu0XVMRGgW+KMJyARNoZfB6CJUKI5FKb28vSktLUVxcDABM+ywhOjoa2dnZyM7OBk3TsFqtWFxchNFoxMTEBCiKQnJyMhPRxMfHb1g3ACk70thdf/62a9rtdr+GmopzswIXFGG5QAl2b4o3QqTCnE4nOjs74XK5UF5ezojKetdVqVSIjY1FbGwscnNzQdM0TCYTE9GMjIxArVYzIpOSkoKYmJgN4bgsZdMCW1i8CSQ0ynZNhWBRhOUCxN9sSjDwTYUtLy9Dp9MhPj4esbGxiIuLC/laKpUKCQkJSEhIQEFBASiKwurqKoxGI+bm5jAwMICIiAgPoYmOjg759cRGDhHLenAVGmW7poIvFGG5gGDPpqy3Mng9QhUWmqYxNjaGgYEBlJaWoqioCGfOnAnpHvyhVquRlJSEpKQkFBcXw+12Y3l5GYuLi5iamkJvby9iYmIYkUlOTkZkZKSg97DRIKLA5/OgbNdU4IoiLBcIFEVhcXERS0tLyMnJ4f0LHYqli9PpREdHB1ZWVtDQ0ICUlBQA/kWKz0HHRqPRIDU1FampqQAAl8uFpaUlJm1mNpsRHx/vITRSIfX8jlCHfCChsdvtsNlsitBsYhRh2eCwZ1OWl5cxNTWFvLw83tcNNmJZWlqCTqdDQkIC9u/f7xEhhHvKXqvVIj09Henp6QAAh8PB1GdIa3NkZCQiIiKwuLiIxMTEsLY2S3GwkvdfrNf2bl/33q7Z0dGB/Px8xMfHK9s1NwGKsGxgfNmyhNs4kqZpjI6OYnBwkEl9iTnFHwqRkZHIysryaG3u7++H2WxGV1cXXC4XkpKSmIjmQmltZiO2sHjjbahpMBiQm5urbNfcJCjCskFhz6awPaLCaRzpcDjQ0dGB1dVV7N6922+KSWph8SY6OhoJCQnQarWoqqqCxWJhIprx8XHQNI3k5GRmhiYuLk6ww06o9F8orwtI2zhARIT8t/d2TX/NAAobD0VYNhiBZlOEtGFZL/pZXFxEW1sbEhMTcdFFFyEiIsLv35WbsLBRqVSIi4tDXFwc8vLyPFqbjUYjhoeHBWlt9n7NcCO1sFAU5fHa/nbR+BIaZbvmxkMRlg3EerMp4UiFsVNfZWVlKCwsXPewkrOweOOrtXllZQWLi4uYm5tDf38/IiMjGZFJTU1FVFQU5+tL9T5ILSykS9Ef6wkNoGzX3EgowrJBIGaCgWZTxBYWkvoymUzYs2cPkpKSQr7WRkGtViM5ORnJyck+W5t7enoQGxvrEdEEit4AaSMWKSAiEYwI+BMaZbvmxkARFplDUl/EkThQu6aQB7i3SC0uLkKn0yE5ORn79+9f9/AU676kxru12el0erQ2d3Z2rmltloNrs5QRC3s9Q6j4Ehplu6Z8kf4Tr+CXYG1ZxIhYaJrGyMgIhoaGUF5ejoKCgqB/Wb2F5ULq/ImIiEBGRgYyMjIA+G5tTkxMZIRGqJ9PsKy3JkHs1wYgaDTBXhFAXkPZrikfFGGRKewoheuBILSwAEBzczPMZnNQqS9f15JbxCLWAePd2kzMNBcXFzE9PQ2Hw4Ho6Ggm8omPjw9L+kaqbjRAmIhlPQIJzdzcHEwmE4qKipTtmmFCERaZ4T2bEsxTppAH+PLyMoDzqZ9gU1++kJuwAOG5p5iYGMTExCAnJwc0TaOjo4PxOiOtzez6jJCtzWykrrEAwkYs68EWGrvdDpPJtKZGo6wIEA9FWGSE98rgYH8RScTC5+mUpmkMDw9jaGgIALBjxw7eoqL8sp5HpVIhIiICUVFRKCkpYVqbjUYjDAYDhoaGoNFoBG1tJlzoEct6r0/mvADf2zUVoREWRVhkADts5+NIzC5shvLv7XY72tvbYbVa0dDQgPfeey/oa/jC13zNZv6FZc8dkdbmwsJCv63NZFAzJSUlqNZmNlIKS7AdYUJDfqcIbI8zQBEaMVCERWL47k1hQ/5dKGkPg8GA9vZ2pKSkoLa2lte1/N2XQuD301drM+k4m5iYQHd3d9CtzezXlctwpBSvv94MDeApNMp2TX4owiIhFEVhdnYWkZGRvDciAu9HLBRFcTZVpGkaQ0NDGBkZQUVFBfLz8z1qNUI0A/iq/cix5hIuuP6cNRoN0tLSkJaWBsB3a3NCQgIjMklJSX5bmzd7xBKMyag/52ZluyZ3FGGRALYty9DQELKzs5GQkMD7umxh4YLdbkdbWxtsNhv27t2LxMRE5s+EjljkJiRSHrKh4t3abLfbmY6zvr4+2O12j9bmpKQknymgcCOHiIXPLFEgoVG2a/pGEZYw4536Eto4krzGehgMBrS1tSEtLQ11dXU+f/GEEgQ5Cguw8aOmqKgobNmyBVu2bAGwtrWZuDanpqaGdS2AN1JHLG63W9BFb1yFZjNv11SEJYz4Whks9OzJekaUNE1jcHAQo6OjqKysRF5ent8Pu1D3JldhkQqxDhfv1maLxQKj0ciIjdvtZupoYrY2eyOHiEVMYfMnNGS75vLyMqxWK/Ly8jaN0CjCEgZ87U0hHyghhWW969lsNrS3t8Nut2Pfvn3rpt8u9IhFCsL1PrBdm/Pz82EwGNDb24ukpCSmtVmr1a5pbRYDqSMWsYXFG2+hsVgszGbXzbJdUxEWkfGeTfEeeFSr1UxaTAjUarXPw2thYQHt7e1IT0/3m/ryRkxhsVgscLlcgjQtbDSk+n61Wi0KCws9WpuNRiNmZmbQ19eHqKgoD6EJtbXZmws9YlkPt9vNiAewdrumv/bmjSw0irCIBHs2JZAti9ARi3cqjKIoDA4OYmxsDFVVVcjNzeX8YfUnUnzuiaZpjI+Po6+vD8D5gjSxnxfyMONyT1IgpW2+9wMNaW0G4Le1mfxckpOTQx6U3WwRi6/XZ9e4/Dk3X0jbNRVhEQHvAn2gD4SYqTCbzYa2tjY4HA5OqS9v1qvXBANJB3Z2dmJxcRF1dXWIiYnB6uqqx2EWFxfHCI1cnIEvBNZrNw7U2jw0NASLxeLR2pycnMy5IUCJWNy8dtGoVCp86lOfws0334yPf/zj4bptXii/tQLDXhnMJZRVq9VM7UUIiLDo9Xq0t7cjMzMT9fX1IR3QQqXC1Go1HA4Hzp49i+joaOzfvx8ajQYul4uxoN+6dSucTqdPZ2Dy1JyYmCjoASFV9CCVw3AwrxtMa3NqamrAn43UB7vUr+92u4OKxH0JzfT0tKSdfcGiCItABFoZHAihIxYAmJiYgF6vx7Zt25CbmxvydYQSltXVVej1epSUlKC0tBQqlcpnXSkiIgKZmZnIzMwE8H77rNFoxOTkJCiKYnbRp6amIjY2dsOkBghySYUFi7/WZqPRiKmpKbjdbiQnJzMRTUJCgkf7+2YWlmAHNL1RqVQwm82IjY0V8K7ERREWAeBjy6LRaAQTFpvNBrvdjqWlJTQ2NiI+Pp7X9fiKntvtRm9vLxYWFpCSkoKysrKg/r13+yzZRc/uaiLRTLArgqVkI0Qs6+H9szGbzUxEMzY2BgCM0NjtdsFeNxT4Hux8WS8Vth7k/RViiDpcKMLCE1+zKcEgVMRCUl9qtRrl5eW8RQXgF7FYLBbodDqoVCoUFBQwfkt87sV7F/3y8jLzxExWBJNoRqnPeCKmpYtKpUJ8fDzi4+ORn58PiqIY1+aFhQUsLS1BpVKhq6tL9NZmX0gdsbjdbt7CZjabERcXJ9AdiY/ymxci7NmU9VYGB4KvsFAUhYGBAYyPj2P79u0YHx8X7AAJVVjm5+fR3t6OnJwcVFZWYnR0lJlIZsMnLaRWq5lDCni/2Gw0GtdsbvRVA9iMXWHhQq1WIzExEYmJiSgqKsLIyAiWlpYQExMjemuzL6QWFiEiJovFIsjDYrhQhCUEKIqCy+USxJGYj7BYrVa0tbXB5XIxqS9SixCCYLvC2CK3Y8cOZGdnM9fxRuiDzrvYzLY3Icu1SH0mJSVF0oFNqVJhUh6uZAcNALhcLiwvL/vsBuTb2uwLvqkoIV6fj7A4HA44nU4lFXahwnU2JRhCFZb5+Xl0dHQgKysLVVVVzAdXyGaAYOZYiKGlw+FYU9+RYvLeVw3AaDTCaDRiaGgIKtX5fegzMzNISUlBdHR0WO5roxbv+eAdMWi12jWtzeQhgG9rszekdVdqYeHz+iaTCQCUiOVChKS+SC4/kMdWMAQrBBRFob+/HxMTE9i+fTtycnI8/lzIQ5zrtYxGI9ra2pCamupzql9qSxd2DYDUZwYGBpj6TG9vL2JiYjwWaolZn5EqYpGK9Q52725Admtzb28vHA4HkpKSmJ9NMG3noW5jFRK+EYvZbIZKpVK6wi402LMpNptN0AU/wQiLxWJBW1sbKIrC/v37fRbzhDa1XM/QcnR0FIODgx67XHxdR05eYWq1GrGxsbDb7di1a9eaYUCr1YqEhARGaLzt5/mwWSOWYF6b3dpM07RHWpOkeonQpKamBrQFkoOw8K2xkFZjKb+HYFGEJQC+ZlO0Wq3gk/JcvMLm5ubQ0dGB7OxsVFZW+v2ghisV5nQ60dHRgZWVFezZswdJSUl+ryM3YfHGuz5js9mYGQ1iP0+ellNTU8PmCiwkUi/6CvVgJU/qsbGxyM3NXdPaPDo6CpVK5dEIwJ5vkoOwCJEK22ifOUVY/OBvNkUM08hAQkBRFPr6+jA1NYXt27czBXF/hCMVtrKygtbWVsTHx2P//v3r7rqQu7B4Ex0djezsbGRnZ3scZEajESMjI1Cr1R7zM8HWZy6EOZZgENLSxVdrM7EF0uv1GBwc9HBtJiIjlbBQFMVLWIGN12oMKMLiE4qi4HA4fM6maDQaOJ1OwV4rUFRAUl80TaOxsZHTh0voVBj73miaxuTkJHp7e1FSUoKSkhJOB4YchYXrQefrIPN2BY6JifHwNwvU0bQZU2FiFs/VajWSkpKQlJSEoqIiuN1urKysYHFxETMzM1hZWQFN0+jt7WXERsilX+tBfheFSIUpEcsGhaS+SNeXrzZioSMWjUbj83qzs7Po7OxkZkG4/mKKVWNxu93o6urCwsIC6urqmI4ertfxPlA30i8JG29XYJfLtaajiT0/I2R9hg8XSsSyHhqNxmO+aWlpCW1tbdBoNBgbG0NXV5eorc3esDMeoWI2mzdURxigCAsDV1sWIS1YAN829729vZiensaOHTsYb6ZQr8cHEk2ZzWa0trYiIiIC+/fvD6k1V24RCyDMPWm12jVmjWRrY1dXF1wu15r5mc2WCpOy3Ze0lRM7IYfDsca1mTwIkEYNIe1f3G4371QcqbFsJBRhATyilPVmU8SIWEge1mq1QqfTAQD2798fUnuhkPenUqmwurqKkZER5Ofno6ysLKRfEKH2umwEoqKiPOozZD0wqc+QnzVFUWG1NtksEYs33oXzyMhIj9Zm0qixuLiInp4eXq3NvtiMU/fAJhcW75XBXAYehY5YyId2ZmYG3d3dyM3NRUVFRcgfZrVaLUgNiHhxWSwWVFdXIysri9f1LpRUWDCoVJ7rgSmKwj//+U9ERUUx9Zno6GiP+Rmx0jJSC4uUxfNAr+3dqOGrtZnt2hzsxtPN6BMGbGJh8V4ZHEwNQ8iIhRy43d3d2LlzJ+8DXIhUmM1mg06ng91uR05OjiD3FMzXL1TUajU0Gg0yMzORlZUFl8vF+JuNjIygs7NzzfyMkGkZKVNhG0HU/LU2k9TmyMhIwNZmXwhhJ6PUWDYAbFuWUByJhYxYzGYzk/ravXt3wFkQrvBNOy0sLKCtrQ1ZWVlISEgQ5GDbyF1hYr62VqtFeno60tPTAbw/cW40GtHd3Q2Xy4WkpCTGsTnYp2U2UtY55ByxBMKXY4O/1mbyMOBdfxQiYjGZTBvKJwzYZMLCZ28KQaiuq5mZGXR1dSE3Nxerq6uCubuGen80TWNoaAgjIyOoqqpCXl4eent7Bfle5SgscsR74pzUZ8ggINvROTU1Naj6jNTF+40QsayHr9ZmYqZJrIGio6M9Ihqhaizrza/JjU0jLMGuDPaHv/ZgrpDlV7Ozs9i1axcyMzMxPj4umSMxcL5Tpr29HRaLBXv37kViYiIA4dJ+chUWKe6J62v6qs+srq7CaDRidnYW/f39iIqKYqKZ9eozUguLlBGLWEu+NBoN8/4DYFKbZNlZV1cX88C4sLAQ8o6gjbY9EtgEwhLqymB/8IlYTCYT2traoFarsX//fuaJUypHYuB8n79Op0NSUhIaGxs9DiehBEGuwiIVoe7tIU/LxcXFHocYuz7Dnp9hH6hSRw0XQsSyHt6pTYfDgcHBQSwtLTE7gtiuzVxraEqNRWYIkfryJtSIZXp6Gl1dXcjPz0d5ebnHh11oYeFyLZqmMT4+jv7+fpSVlaGwsHDNe6MIi3zxV58hbbNOp9PDqHGzHO7eSLmLJTIyErGxsaBpGtu3bw/Y2pyamoqEhASf96oIi4xwu91YXV3Fe++9hw9+8IOCuhEHIyxutxs9PT2Ym5tDdXU10z/P55qB4JIKc7lc6OzsxOLiIhoaGpgp5VCuxfWeFGE5j1jvg6/6DGkEGBsbA0VRiI2NRUREBDM/Ey6hkToVJrWzMXl9f63NRqMxYGuzxWJR2o2lxns2xWq1CpoG0Gg0zPKg9a5pMpmg0+mg1Wo9Ul/ehDMVtrq6Cp1Oh+joaOzfvz9g04BQg41yFBY5dIWJeX1Sn8nLywNFUWhra4NKpcLc3BxTn2F3M4npn7VZoyXAf1eYr9Zmk8nERDQjIyM4evQoxsfHMTExgaWlJcHOsTfffBMPPfQQmpubMTMzg2eeeQZNTU3Mn9M0jXvuuQe/+c1vsLS0hIsuugiPPfYY417AhQtKWLxnU0ihTMhwmFxnvaLg1NQUuru7UVBQsO7EerhSYSQdV1RUhNLS0nU/pGKlwtxuN7q7u7GwsMAcbqmpqWE1BwTkXbwXErLuITk5Gfn5+XC73cz8DCkyx8fHMyLDZ2OjL6SOWMQq3nPB7XZzGnpVqVRISEhAQkIC09ocGRmJ559/Hv/4xz/w5S9/Gffddx8uv/xy3HTTTbj++utDviez2Yzq6mp89rOfxY033rjmz3/yk5/g4YcfxpNPPoni4mJ897vfxdVXX43u7m7Odk4XhLCstzKY6w+XC+RD6u9JhBya8/PzqKmpYTyk1rummF1h7E40rvfk71qh3hM5UK1WK1pbW6FSqVBWVoaVlRVm7zk53IhLsBzMG8VAaq8wjUbjsRrY4XAwbc3sjY3kZ5GQkMDrnqWOWMQ0meTy+qEIm1qtRmNjI/bt24ennnoKL7/8MsxmM1577TUMDQ3xuqcDBw7gwIEDPv+Mpmn8/Oc/x3e+8x0cOnQIAPDUU08hKysLJ06cwMc//nFOr7HhhcW7QM8WFVKsF3p/CgCfBy5JfUVEROCiiy7irO5iGEcSLBYLdDodVCpVwHQcl2uFChEWMnyZnZ2N8vJyuFwupuZEDjf2cCA7VbPRFh3JjUBplMjIyDUbG4nQjI+PA8Ca+ZlgfhZSRyxyTIVxhUz/p6WlYd++ffjQhz4k4N2tZWRkBLOzs7jiiiuYryUlJWHv3r04e/bs5hAWLrMpWq1WUGHxt+xrcnISPT09KCwsRGlpaVAfZiEjFnYqbH5+3mPrZLC/YEKlwkjLd2trK7Zt24bc3Nw136/34UasNIxGI4aGhqDVapknaCnSZkIh5T4WLrBz/3l5eaBpmpmfmZ+fx8DAQFD1Ga71SLGQQyqMj7DZbDa43e6wTd7Pzs4CwBobp6ysLObPuLAhhSWY2RShvb3INcnB6HK5mHpBbW0t0/4Z6vWEurf+/n6MjY1h+/btyMnJCelaQkRSLpcLvb29oGnaY/hyvddlW2mQCWej0Yjx8XF0d3d7zGxstLSZ1KmwYFCpVEhMTERiYiIzbe49BBgfH+/xs/CenwGkWw280SMWs9kMAEq7sdgEO5sitBsxuSZpZ9bpdIiMjAx5TwkgrLA4nU44nU7Mzc2hsbGR1weSbyrMZDIxe1wAcBIVX3hPOAdKm6Wmpsp6295G3yDpqz5DWmb7+vpgt9uZ+gwxaQSk68KTWlj4RkxmsxkqlSps6xXI/qe5uTkPG5m5uTnU1NRwvs6GEhayNyWYCXq+Fiy+UKlUmJ2dxcTEBIqKirB161ZeH16hhMVoNKK9vR0A0NjYGJJ9BBs+qbDZ2Vl0dHSgsLAQubm5+Mc//sHrXtj4S5sZDAbOaTO5Co9YiJWOioyMRFZWFrKystbMZoyPjzOfn5mZGaSnp4d1fgaQdkBSiNcnw5Hhes+Ki4uxZcsWvPbaa4yQrKys4N1338Wtt97K+TobQljYsyn+Vgb7Q2hhIfcxOTkZcurLG77CQtM0RkdHMTg4iOLiYsZ1lS+hCAtJw01OTmLXrl3IyspiZonEONy4ps3YrbTkF12qduONlAoLBl+zGQaDAe3t7VhYWMDw8DAiIiKYn0U4amVSRyx8U2Fke6SQPzuTyYTBwUHmv0dGRqDT6ZCamoqCggJ87Wtfww9+8AOUlZUx7cY5OTkesy7rIXthoSgKLpcrZFsWIYVldXUVra2toGkaVVVVgogKwE9YnE4nOjo6sLKygj179iAyMhKDg4OCHCTB1ljsdjva2trgcDjQ2NjITAuH8yDlmjYjTR1SFpbDiRTfJxF9AKitrWWWxxmNRqbFPC4ujvl5JSUlCfJAxEZqYeGbChNj6v7cuXO47LLLmP++/fbbAQBHjhzBE088gbvuugtmsxmf//znsbS0hIsvvhgvvfRSUKl+2QrLerMpXBFCWGiaxuTkJHp7e1FcXIyFhQVBP6yhCsvKygpaW1sRHx+P/fv3IzIyEna7nblnvgdJMDUWYmaZnJyMuro6jwOC3IcUh5u/tNnMzAzMZjPOnDkT1idoYGMV7/lCZlhUKpVP0SeDmuz6zHreWcG+vtQRC99990LXDC+99NKAv9cqlQrf//738f3vfz/k15ClsISyMtgffIWF7atVV1eHtLQ0LC4uCr6eONjrkfbmkpISlJSUeMzuAML8QnFNhU1MTKC3txelpaUoKiryaWYJSFe4Zt8HSZtFRERgenoaxcXFPtNm5Ala6ENpoxfvQ3ldf++h9/559vzMxMQEaJr2aGsO5YCVUljIwzHf4v1G6wgDZCgs7NkUMjPCBz7CsrKyAp1Oh5iYGA9fLaHrNmq1Gg6Hg9PfJZP9er2eETo2Qh7i66XCiMHm/Py8z3sR456ERKVS+U2bdXV1idZtthkjFi7ExMQgNzfXwzvLaDQy2xqJgSYRGi7L8aTeXglAkBrLRkM2wiL03hRCKCJA0zQmJibQ19e3JiIAhG0PDuZ6ZJUxMbX0lfMM5AwQyn35EwO2Nct6rdZyFBZfn631us1I4Tkcxo1iILeIJRBs76zCwkKPbY3e9RnSlOGrPiPlgCQ5d+RWYwkHshAW8nQyMTGB4uJiwUQFOP9DdTqdnP++0+lEV1cXFhcXUV9fzzzNshF66JKLsMzOzqKzsxN5eXlr9rmwIe+bmHb3xJply5YtqKqqWvfg8CcsUhfN18sz++s2I4OBoaTNpBJXqfy6hHpddn1m69atcDqdjBMwWaKVmJjICE1iYiITcUu5Cwbg9zlXUmEhQlEUHA4HrFYrhoeHsXXrVkGvr9FoYLPZOP3d5eVltLW1rUl9+bpmuCIWiqLQ19eHqakp7Ny5c43VgjekHiWGKzFN0xgZGcHQ0BCqqqqQl5fH+Trk33uzUbqyAnWbBZs222ypMDEO9oiIiDX1GV+7TYDztihRUVFh//5JqzGf1zWZTIqwBANJfZGur4iICFHaP7lEF+xtir5SX6FcM9h79CUsNpsNOp0Obrfbo3031OvxuS+Xy4WOjg4sLy9jz549SEpKWvP3VQMDUA0Pg966FXRp6ftf9yMsG0VUfBFq2kzKdOBGSoUFS0xMDGJiYpCTk8NkQBYWFmAwGJj0Mbv7j0t9hi9CpOHMZrPfRXxyRhJh8WXLwt6dImQv+3rRhdPpRGdnJ5aWlvymvnxdM5j0Wij3uLCwgPb2dmRkZGDbtm1BfUCFEhYSsRBrFrIcbE1twWiE9sgRaF59lfmS+8or4XrqKYD1SyGnGouQBJM2oyhqUw1mSpGCI/WZqKgojIyM4KKLLmIaAaamptDT04O4uDgPfzOh52cAYab+rVYr58yAnJBEWMgHjd1GLKawkLZlb5aXl6HT6RAXF4eLLrqIczE2mPQaF9hCQNM0hoaGMDIyElS6yft6QnaFnT17FoWFhSgrK/PtIH3kCNSvv+55D6+/Du3hw3A9/7wsi/diEihtRlyew+1tJsd2Y7EhD65arZax/QfOP0yS+Rl2fYb8TBITEwW5Z75T94DSFRY03oefGLtTAN/RAE3TGBsbw8DAALZu3Yri4uKgfunE6gpzOBxob2+HxWLh7ATsCyFciSmKwsjICABg586djDndmtcaGPCIVJivu93QvPoq3IODoP+1rXKzCIs37LSZXq9HZWUl7HZ7WLvNNlPEwn5tX41AERERyMjIYBbekfrM4uIiOjo6mPoM311AQqXClBoLT4TenQKsbTdmW6A0NDSElL8UY47F6XTizJkzSEpKQmNjI6+td3yFz+FwQKfTMVEZKZD6QjU8HPBaqqEhWQqLVPejUp3fR5+VlSVot9l6bMaIhWvjgHd9xpepKXt+hqu1iRCpMIvFoggLXwKlrfhck4gAsR1JSEjwXSvgiJARC03T0Ov1sFqtqKio8Dm5Hs77W15eRmtrK5KTk7Fjxw68+eabAQ9guqQk4PXof3X5+ZvG3+wI2W0WiM0csQSDd72M+JstLi5iamoKvb29iImJYUSG+M75QohUmNlsVlJhweDrwyaGxT0Rq5GREQwODvq1HQkGobrCiF2MwWBAZGQkiouLeV8TCP1p3NuahYh8QGEpK4P7yiuhfv11qFjvCa3RgLr8cqY7zNc9bUZxWe/nItaQphKxhIZarWYEpKSkxKM+MzQ0BKvV6uGezY4w+abCyM8/XNsjhURWEYtWqxU8YiEWMWNjY9i9ezfT284HIeZYSKdVVFQUdu3ahY6ODt73RQg2YvFnzcJ1it/11FPQHj7sUWuhLr/8fFfYv5BbKkxKuB7wQg5pbtaIReipe+/6jM1mY+ZnpqenmQgzJSUFVquVt7ApNRYBEDpiWVxchE6nA3B+8ZVQvet8I5bp6Wl0dXWhqKgIpaWlMJlMYZ/kJ1itVo/3iL2pjnM3V0oKXM8/D/fg4PmaitccC7mWnIRFqsOOz3sQatqMvKYSsQhPdHQ0srOzkZ2d7RFhErEh7z/5uQS7ZVZJhQWJmKkwsRZfEUKNWCiKQk9PD2ZnZ1FTU8M89QjdZcb1ECfDY/6sWYJtE6ZLS9cISrD3tBkQ6oDnmjYjDSqbLWIJx/bIgcUBjCyNoCS5BKUppR4RZm9vL1wuF2JiYjAzM4O+vj7ExMR4zM8EatIhP1MlYuGJEKkwh8OBjo4OmEwm7N69GwkJCRgcHBSkkEYIRQAtFgt0Oh1j2siODEjrtVDpivWEii28gWZlyJyRmL5jCsKwXtoMAHQ6HdLS0kRbCeCLCzViMVqN+NyLn8NrY68xX/tQ4Yfwu4O/Q0r0eSGnaRpxcXFM7dTlcjFtzez6DBEa75+JxWIBTdNKjYUvfCOWxcVFtLW1ITExEfv370dERARzmEmVagKA+fl5dHR0IDs7G5WVlWs+7ETwhMoJB7o/LtYsbMTwHSMWOgMDA4iPj2cOu3Du9ib3EW7C9ZrstFlRURHefPNN5ObmYnl5GV1dXXC73cyshphDmlLXWMQSls+9+DmcHj/t8bXT46fx2Rc+i2c+8gyAtV1hWq3Woz5jt9uZtBlJZZKfic1mYwRFrIjl3nvvxX333efxtYqKCvT29vK+tuxSYaFELGxzxLKyMhQWFnpM9wtdu+F6PZqmMTAwgLGxMWzfvh05OTk+/55QXSQEf2KwrjVLENcK9Z4oikJXVxf0ej2qqqqYX67R0VGPwzBc2xylINwHLfn5ZWVlMbtOwrUS4EKMWAYWBzwiFYKbduO1sdcwuDiI0pTSdVNxUVFRa+ozpDbzqU99iqnR/OlPf8JVV12FwsJCwb+X7du34+TJk8x/C1UykFXEotVqmdW6XCHT6maz2e8TuBimkeulrsj+d7vdjsbGxoBPHULuUCHX877W3NwcOjo6kJ+fj/Lycs6Hm1D2MMD59+Tdd98FcL5RgHzfeXl5HvvQ2dscSTQjlM3GZsS7eC/WSgBfSLkPRSxhGVkaCfjnw0vDjLBw/d7ZP5P8/HycO3cOf/jDH/DNb34TTz75JL74xS+iqKgIjz76KK666iohvg0A589cf64avK4r+BV5EGxkYTQa0dbWhuTkZCb15e+6QluwAP59zch9paSkrNn/7gshd6iQ+2N7j/X392NiYgI7duwI+kMkVI2Fpml0d3cjKysL27ZtW7M1kz0vsHXrVjgcDhgMBhiNRnR0dDCdNURogu2ukQtSpt/8PUx4d5vZ7XbmyZlv2oyiKF4uEnwQS1iKkwPPm5UklzCvH6qoRkVFYfv27UhLS8Nbb72F1dVVvPnmmygvLw/pev4YGBhATk4OoqOj0djYiAcffBAFBQW8r7shU2E0TWN4eBjDw8MoLy9HQUFBwA+60BP97JqI932RonhFRQXy8/M5/QKSFcxCCQtJOzkcDrS1tcFms2Hfvn0h5WqFSIVNTEwwLq3bt2/ndM3IyEiPNMHq6iqMRiPTXRMbG8scdMnJyUH/Aks5nClVKozr60ZFRQk2pCnlagSxoqWylDJ8qPBDOD1+Gm76/QdhjUqDSwsuRWnK+c5Ivl1pFosFsbGxAIDExERcd911/G7ci7179+KJJ55ARUUFZmZmcN999+EDH/gAOjs7eTcMyCpi4eIVZrfb0d7eDqvVyqn4DIgbsRDYHmShDGIKKSxqtRpWq9XDeyzU3CkfYWG3V8fExCAzMzOkQ0alUiExMRGJiYmMIwCZ3+jt7YXT6URycjITzYTDLTgUpOqK4zPHwjdtJvXOebFe+3cHf4fPvvBZj1rLpQWX4ncHf8f8N99OVDLDItZn+cCBA8z/v2vXLuzduxeFhYV4+umn8bnPfY7XtSUVFu9Da71UmMFgQHt7O1JSUlBbW8v5sBS6eO8dYaysrECn0yE2NjZkDzIhhYU8YZaXlwtiXxPKfdntdmZJ2f79+9Ha2rrmOqGKllarZbYH0jQNi8Wy5omaiExqaqrfz4lUB70UEYtQrxls2kzq1cBi7FkBgJToFDzzkWcwuDiI4aVhZo6FDd+IKdzbI5OTk1FeXo7BwUHe15JVxOIvZcXeURJMiol9XbFckycnJ9HT08Np82QghBAWiqLQ3d2NxcVFZGRkCOI9FsrhT4wsU1JSsGPHDt7rWde7v7i4OMTFxSE/Px9ut5vxchoZGUFXVxcSExMZoUlISNiQU/d8X1es73m9tBlwvsEmMjJStJUA/giHqJWmlK4RFALfVFi4p+5NJhOGhoZw8803876WrITFVyqMnfoKdUeJGMKiUqkwMDCA5eVlD3+tUOErLGxrFtJlJQTBCsvU1BS6u7vXmH2Ga0BSo9EgLS2N+XnYbDbmoBsfH4dKpUJqaioiIiIETY8Gw0aOWALhK23W0tICrVYr6koAf0gZLZHV60KkwsTi61//Oq6//noUFhZienoa99xzDzQaDT7xiU/wvrasU2EGgwFtbW1IS0sLKvXljdDCYjab4XQ6YbVasX//fkE6lPgIC3mfMjMzUVVVhdHRUZjNZt73FMx9URSFvr4+TE9Po7a2Funp6R5/LtXkfXR0NHJycpCTkwOKorCysgKj0Yi5uTlYrVa89957YZ9GDzdSFdA1Gg00Gg2ysrKQnZ0taLcZF6Su7wDgLSxipsImJyfxiU98AgaDARkZGbj44ovxzjvvMAOcfJBdxOJyuUDTNAYHBzE6OorKykrk5eXx+sAJKSyzs7Po7OyEVqtFeXm5YG2voczasLvQKisrkZ+fD0C4FmFyrfUEgSwGczgcaGxsZDpZgr2O2KjVaiQnJyM5ORmJiYkYGhpCQUGBx0FH7DXS0tI8bHeEQMrvX8r0H3ntYLrNSFTJB0VYAvPnP/9ZtGvLSlg0Gg1omsZ7770Hh8OBffv2CeKTI4SwUBSF/v5+TE5OYseOHRgeHhZlPTFXyC6XpaWlNd1xQg41ricIKysraGlpQVJSUsCZHTkIizcqlcrjoDOZTDAajZifn8fAwACio6OZaCYlJUWw1tULNRXmC3+HeziGNKVuHADAu8bCN8UuFZKnwtgsLS0BOD/DUF9fL1hHh0ajCXqin43NZmM6nBobGxEXF4exsTHJhGU9axYxZmJ8MTMzg87OTk6NC3IUFvb9qFQqJCQkICEhAYWFhYxhoNFoRH9/P+x2u0dLcyhtoBdi8Z7La3M5XH11m7F9tEJJm0k59U8K93zed7PZLIqNSziQRcRCURSGhoYwOjoK4LwRmpBtgnwsXUj9IiMjA9u2bWM+qELbxHCdtWFbs5SVlfl9GhTTHoY9zV9dXY3MzMx1ryNHYQmEt2Egu6V5eHgYWq3Wo6VZqulyLkgdsYTy2r58tIxGIxYWFjinzaROhW3WtcSADITFZrOhra2NSX298847gnfrhJIKY0/3+7KWF2Pocj2re2JouXPnzoDWLGKmwpxOJ9ra2mC1WoOa5t9owuJNbGwsYmNjmY470tI8NjbG+JqR2kxiYuK60Vs42QgRSyBCTZtJnQrbrNsjAYmFRa/XQ6fTIT09nUl9ibGeOFhhIcaWFovFb4uz0BFLoCiDbc2ynqEluTcxUmGrq6tobW1FfHx80NP8/oRFykMvVNRqtc+0jcFgwOTkJAAwf56WlsZsLt2MqTAxbPP9pc28u82cTiccDock378Q+58sFosSsYQCRVEoLy/3iAbEGmbketAuLS1Bp9MhMTERjY2NYTO29Hc9MmwYjDWLGKmwubk5tLe3M+uUg/1F3egRSyC80zakpXl6eprxNSORDKBELELjL21mNBrR3d2NwcFBQbvNuCBUKkyJWEJgy5Yta6ITMafkA0HTNCYmJtDX17dmuM8XYljxe4sBmerfunUriouLJbG6B85HlktLS9i1axeysrJCvo6chEVMJ4CkpCQkJSWhuLgYTqcTi4uLMBgM6O/vBwB0dHQwQ5wxMTGiH/oXWsQSCHbabHh4GPX19XA4HKKsBAgE31QYEciNuD0SkEGNxRspUmEulwtdXV0wGo2or69nQuz1rilWjYVt3uhr2DCYa/HB5XJhaWkJFEXxbv2+kCOWQERERDC+Zna7HW+//TaSk5OxsLCAwcFBREVFebQ0i+FtdaFHLP5el6IoaLVaZq8P4Jk26+zsBEVRogxpCpUKUyKWEPBnnR/OiIW07kZFRWH//v1MPnw9vPeJ8IVEQDabDa2traBpGvv37w9pSE+IVBh5X4DzFjF8n5zkWEcJt9CR96CgoABFRUVwu91MSzPZgZ6UlMTUZoRa1byZIhYC+dl6i5p32ozMLgXTbcYFIYRF6QoTkHAKy/T0NLq6ulBYWIiysrKgjS2FjlhWV1dx5swZxpol1A8m31TY/Pw82tvbkZ+fD4fDIcgTp5B1n42K989Eo9EgPT2diUitVivTBDA2NibYqmYpmwakiljIZy3Qa3vPLrENTEdHR3mlzfjWWCiKUmosQhKOVBhFUejt7cXMzAznOQxvhKyx0DSN5eVlGAwGbNu2jbFmCZVQU2HsFusdO3YgOzsbXV1dgu28VziPv/ciJiYGubm5yM3N9VjVPDEx4bOlmeshJ1XE4i9qCAehWKp4G5jySZsJYUAJQKmxhEI4U2EURYGmaWaKnqZpv75WwVyTL2xrlpSUFN6iAoQmLC6Xi1lUxm6xFirS2Kw1llDxtaqZRDPsVc3kkAuUMpVaWKR4bfKZ5fPaXNNmaWlpSElJ8UibCbE9EoCSChMKsYQFOD+13tXVhS1btqCqqorXD16IiMVsNqO1tRWRkZEoLi5mLG34EuwhbrFY0NLSgsjISDQ2NnqkXIQSBO/rqFSqTbUeGOCXkoqMjPTwNSOrmmdnZ9Hf34+YmBimCcB7VbNUwiLE4c7ntflaqrAJlDYbGRlh1vkSoXe5XLwMas1mMyIiIjjXfOWG7IRFq9Xy8vXyBRGQjo4ObN++HTk5ObyvyTdiYdcxysrKMDU1JaoNiz8WFhbQ1taGnJwcVFRUrBFboVJ+SsTyPnwPO1+rmklLc19fHxwOB5OySUtLk10BPRyIPXW/XtrM5XIhJiaGiWqC7TYzmUyiriUWmws+FWa329HW1gYAqKmpEWTXAMCvjuHLmkXonfekcOrvg8m23N+2bRtyc3N9/j2xIhbgvKgBQEpKimQtqRfC67F9zbxXNQ8PD0OtVkOj0WB+fn5NykZM5BCxhAvvtFlrayu0Wi2ntJkvNnJHGCDDiMXfeuJQWFxchE6nYw4uoXanAKEJINsqxtuaRcguM+YXub8f6tFR0Fu3gi59f32q2+1GZ2cnFhcX11ju+7qWUDUWX3M6KpUKbrfbwwJFyJ+T3BD7kPW1qnlgYGDNqmbyXou5qplESlIIixBeXaFCvuf09HTk5OSsmzbz1W1GhEWJWELE+0nW13riYGE/jZeXl6OgoACnT58WvD04mPtcWVlBa2srEhMTsX///jWDcIJGLEtL2HfffYj+1xwKALivvBKup56CNTqaWRfb2Ni4bg5XqCl+8nMmS8GcTif27NkDrVYLi8UCg8HA1AuIBUpaWtoFu9UxXGg0GsTGxsLpdGLnzp0eq5onJiaYVc3kf0Lm9KVqNQakdTYmr0/qXFy6zUgjRkpKCmJjY8MWsTz66KN46KGHMDs7i+rqajzyyCPYs2cP7+tKLize8E2FOZ1OdHZ2Ynl5Gbt370ZycjKA8wekkG3MwUQYZA98IGsWIec8Yj73OcT+K/1HUL/+OuhPfAJn7rwT2dnZqKys5PSLJ2QqzOFw4OzZs0hMTERdXR2A891opChaVFQEp9PJHHzEUPBCiWbkYELpvap5dXUVBoMBU1NT6OnpQXx8PPNe8xV1qWo75LWlFJZAEZO/bjO9Xo+2tjbcfvvtyM7OZlKaXJxAQuEvf/kLbr/9dvz617/G3r178fOf/xxXX301+vr6QhrBYCNLYQlVAFZWVqDT6RAbG7tmAZYYA43rCWAw1ixC3Z9qYACakyfXft3tRvTp09j+7W9jy7Zt3K8nkOCZzWbMz8+jpKSEMbF0Op1r/l5ERASysrKQlZXF/NKJEc1IdeBJVUT39bpqtZrxNSspKYHD4WCaAIRY1byZIxaucyze3WaVlZX40Y9+hF/+8peYmppCRkYGGhoacNVVV+GOO+5gHpSF4Gc/+xluueUWfOYznwEA/PrXv8YLL7yA3/3ud7j77rt5XVtyYREqFUYMG/1tMxS6KWA9ISDzMhRFcbJmESoVphoeDvjnOYODoD7wAc7X45sKo2kaIyMjmJubQ3JyMsrKyjj/W/YvnRjRzIVSvOfyulwELTIyco2o81nVvJkjllAn72NiYvDhD38YIyMjKCgowMMPP4yTJ0/i9ddfD8neyR8OhwPNzc345je/yXxNrVbjiiuuwNmzZ3lfX3Jh8SZYAXC73eju7sb8/HzAqEBoYSEHrq8PsNFohE6nW7N1cr3rCSEsdElJwD+PuPVWuI8fh+upp4CUlHWvxycVxm4SyMvL452KDFc0IyZyilgC4WtV89LSEgwGAwYGBmCz2Txamn0VmqWOWKRaSwwIM3kfHx+PnJwcHD58GIcPHxbw7s53ZLrd7jVu5VlZWejt7eV9fdkJC4lYuPwymM1m6HQ6aDQaXHTRRQGfWMWIWADPJyOapjE2NoaBgQFUVFQgPz8/KKt7QYSlrAzuK6+E6rXXoPZzPfXrr0N7+DBczz+/7vVCTYURM02VSoXGxkZMTU1hZWUl6OsEui8u0Qx5wt7ItRm+CDEgqdVqPXzNSP6f+GqRAjXbvHEzRyx8u9KUdmOe+EpZAed/MIEsxMnu97y8PJSXl6/7QxQjYgHe79Un1iyLi4seTQPBXE+oGtDoD3+I+M9/HlmsrjA2KrcbmldfhXtw0KMN2d99BRuxLC8vo6WlBenp6di+fbugE9D+8BfNzMy8h6GhQURGFiMtrZqJZqRAygFRod9/71XNxOuO7DxJTExEdHT0uvNUYiFluzGxj+ITsVgsFsFm7nyRnp4OjUaDubk5j6/Pzc0FXHvOFcmFxRvyw3C5XD6FhaIo9Pf3Y3JyEjt27OD8JoglLG6328OaJRjrfe/r+UutcUE1MAB6cBADNI2J6GjQP/gBLurtRdIdd/j/N0ND6wpLsKkw4hhdVlaGwsJC5kAJ5+S9SqVCTIwTTudtAF4DsYNbXd2H2dnb4HbHIj4+Hi6XC3a7Pay2GRslFRYMbF8z4P122unpaVitVrz11ls+VzWLiZQRSygGmN6YTCaUrJPW5kNkZCTq6+vx2muvoampCcD5+37ttdfw5S9/mff1ZScsarXab8eVzWZDW1sbXC4XGhsbgwoVhRYWlUoFtVrNFDa5Rk7+YEdAQV3DaIT2yBFoXn0VALADQMXll+PNW2+FY+/egP+U3rp13ctzFQSaptHf34+JiQmfDge+riPmYTcy8jmsrJz2+BpF/RNZWY9jy5anMDk5iZWVFZw5cyZstRm5F++FgrTTkoel8vLyf0WPM8yqZnZLsxi1ECmFhZwzfE0oxU6F3X777Thy5AgaGhqwZ88e/PznP4fZbGa6xPggubBwtXUxGAxoa2sLqiC+3jX5QA6J/v5+xmKeD96pNa5ojxyB+vXXPb/2xhuoXlmB49ln4b7ySqhffx0q1vdOazSgLr983WgF4FZjcblcaGtrg9lsxr59+3zukAhnxGKzDWBl5TUff+LGyspryM+fQ3Z2NoxGI/bs2eO3NhOup2uxkXInilqtZnzNvFc19/T0wOl0rmlpFkIEpRYW8uAZKuHYxfKxj30Mer0e3/ve9zA7O4uamhq89NJLvNaPEyQXFl+wZ1nYO0KqqqqQl5cX0jWF3PhIrFlomsb27dt5iwoQmrCoBgaYSMXj62430s+dw+zQEFxPPQXt4cMef4+6/PLzXWEc7yuQIBBn5KioKDQ2Nvr1QAqnsNjtI+v8+TCA80Vo79oMGRhkP10LGc1ciKmwQK/r/X6xVzWTve5sK/rIyEhBVjVTFBU2TzRfry3E9shwLPn68pe/LEjqyxtZCgvpDHM4HOjo6IDJZPLYEcLnmnwh1iwJCQmIjo4WrNsoWGGhKArTp08jUDJLMzIC7NkD1/PPwz04eL6m4uUbth6BBMFgMECn0/l1Rl7vOmIddlFRxev8eQlstrVfZ7sGk6drEs0Q6w0+0cxmSYUR1usKU6lUiI+PR3x8PAoKChhPLYPBwHtVs9QRC9/XVrrCeOIvFba6usp0l+zfv5/304cQ9u/EmoUMYb799tuCTvNznb53OBxobW1FREJCQGFxFBQw/z9dWhqUoBD8pcLGx8fR19eHyspKTsvJwhmxREeXITHxQ/+qsbB/5hokJl6K6OhS2GxL615nvWgmLi4uaPuTzR6xBMLbU4usajYajRgbG4NarfZoIQ+0qlnKORa+Mywkktuo2yMBGQiLNzRNw+VyYWBgAOXl5SgqKhLkl4KPZQp7lTG7MC1Gp9l697iysoKWlhYkJydjx0c/Cvef/uSzhrJYXw9HYaEg98QWBPJezM7Oor6+nrOPUbj3sRQX/w4jI5/1qLUkJl6K4uLfMfcTDGJHM2IiVaTEd47Fe1XzysoKY5653qpmqbvC+IpaOIr3YiIrYXG5XOjq6oLVakVBQQGKiwOnNIIhVA8ytjWL9ypjIWdPuFyPtPKyzSz91VD6brsNmQKvFGY7Ezc2NgZlMeFPWMTbUZKCsrJnYLMNwm4fRlRUCaKjg4/Y/BFKNLMZU2FCHe5qtRrJyclITk72uaqZCDv5n9SpsI1SYxEL2QiLyWSCTqdDZGQk0tPTBX/iCyViWc+aJVwRC3t2Z00rb0qKzxoKde6coHb3JpMJLS0tSEhIQF1dXdBFVV/CEo6DNjq6VFBB8QXXaIb9UBJOpOoKE1PQvFc1k4HYubk59Pf3M68bFRW1ZlWz2PCtsbjdblitVkVY+KBSqTAzM4POzk4UFhaitLQUPT09glrcA8GJAFdrFqHW9rKv5y0sDocDbW1tsNlsAWd3vGsoQkVTZN3AO++8w/x8Qj0s5LaaWKz78RfNzM/Pw+Fw4L333hPMmp4LUqbCwiFo3vY+LpcL7733Hmia9rmqOdg1wcEihE8YAKXGwofV1VV0d3ejurqa2QEgdCQQzDVJOs5oNK5rzSKGFT/7equrq0yU0NjYGFSUIISw0DSNqakpuN1uVFdX82qr9pcKInl4ORpGCgE7mklJSWEeoMJZm9koxXuh0Gq10Gg0yM/PR1pamoev2fDwcNBrgoOFb42FCIsSsfAgISEBl1xyicehqdFoYLfbBX0dLsJCrFkiIiI4WbOIWWOZnZ1FR0cHiouLsXXr1pCKzXyeVN1uN7q6urCwsAC1Wi3IrI53EwAxGyXRKXFduFBFhhy0vqKZ6enpkDvNuLyuVDUWqTqzSLTka1Uz8TUTa1WzEAaUUVFRIc/wyAHJ71ylUq15A7VaLaPaQrGesMzPz6O9vT0oaxYxaixutxv9/f0YHx/3iOJCuVaookeciQGgrq4O7777bkjXYUN+WYkpIXnfIiMj4Xa7QVEU8z9y/2R3+IUqNOHqNNtsEQvgX9Q0Gg1T4AfArGo2Go3MquaUlBSmpTmU95xvKsxkMm3offeADITFF2KlwnyZPNI0jcHBQYyOjgZtzSJ0xAIAo6OjcLvdfq1RuBLqvRFn4rS0NGzfvh0Oh0PQJgC2eGg0GqhUKo8VBERkyN8l/5YIjVAHlRw3SPqrzbCjGXLgBRPNyHVAUuzX5vL+iLGqmaIoXtGG2WyWrNFDKGQhLN5pGz7rif3BtuMnHw6n04n29nbG4yrYYpmQKTuTyYSVlRVER0cHtEbhSiipMNJEUVpayswPsSMNIVIEDocDWq3W5y8oOw1GxIUIDftBY6OmzIL5eQgZzWzWiCXY1/Ze1exrxw/xNQvU5ed2uwMOb64HaTVWIhaBEcp+hQ3b5j4iIsLDmiXUg1yoiIWk4aKjo5GbmytIMTGYe6NpGgMDAxgfH1/TziyEsNA0zfyivfXWW0hLS2OWRvmzxCE/L3/RDHnw4BPNSNEtFep7yCeaUSKW0PB+z81mMwwGA/R6PbOqmYh7cnIyE6UI0RW2kYcjAZkKixipMHL4uN1uZtCQWLOE+uHne580TWNoaAgjIyPYuXMn5ubmBDvsyPe6Hi6XC+3t7TCZTD7TbyHb+f8LEnVER0fjoosugsVigV6vx8zMDHp7exEXF8eITKBUg3c0w/7fhRDNBIOvaMZgMMBoNPqMZjZbxEIePoR8bbav2Xqrmu12O6/XNplMG7ojDJCJsIQjFUauOzAwAIPB4HNnSLDwiVhcLhc6OjqwsrLCpOH0er1gNRsuk95sZ+J9+/b5DN/ZEUswkCI9OfyJsJNfTvaBuLCwgLa2NtA0zYhMWlqa33SCr5QZeR2hohkxEHNuhj0s6B3NqFQqzM3NITIyMixzMwSpIhbyPov5fQZa1byysgKLxYKVlRUmigwmC2GxWJQaixiIkQqz2Wxwu91YWVlZY80SKqFGLKStmVjNkwNUyLmY9UTPaDSitbUV2dnZqKys9PtLGIqweBfp/a0m9j4QV1ZWoNfrPdbbpqenIyMjw2/O2V/KjNyD3KIZsQ9aX9HMe++9x6zODqenmZQRCyCusHjDXtX83nvvIT09HRRFeXyW2S3Nge5to9u5ADIVFnJgCxXCLy4uQqfTQa1Wo6qqSrCngVAiFr1ej7a2Np9tzVyWaglxbxMTE+jt7eXkTBysnT9JfQX71KhSqZjCaWlpKWw2GxPNjI6Oejwhpqam+u268dcA4C12RHDC6SklRU0nIiICERERKCgoQFpammCdZlyQKmIRYoMjHyiKQlJSEuPSTFY1G41GZo8Tu6XZu86opMIEwvvDp9VqmYOAr/30+Pg4+vv7UVFRgfHxcUF/uYO1iRkZGcHQ0BC2b9+OnJwcn9dzOp2C3JuvrjC2S3MwzsQAt0OR3cXlL0rhCmlkIM62i4uLWFhYwMDAAKxWK1JSUhih8VfoDNQAoNfrmZQre15G7MNIStv89Woz7ANPiGhGyoiF3dEYbryL92RVc3Z2tt9lcqmpqYwtjRTOxkVFRRgbG/P42oMPPoi77747pOvJQli8YbcGhyosbrcbnZ2dMBqNaGhoQEpKCqanp8Nucw+ASUMsLS1hz549SEpK4nW9UO6NeI7Z7fagUoHkF3Q9YWFHBnxFxRu1Ws0cdhUVFbBYLFhYWMDCwgIGBwcRHR3NiExKSkrABgAA6O3txdLSEqqrq5n044U8nOkv8g9Um+nt7UV8fDyvaEaqiEVKZ2Py+v7OLV/iTlY1/9///R8eeOCB8ysxduxAX18fysvLw/Yefv/738ctt9zC/DcfrzJZCgv5ULhcrpD6wS0WC1pbW6HVaj2sWYTuNuNyPfa9NDY2BnwKFEtYiDNxfHw89u3bF/TwVqD78lekF5PY2FgUFBQwWwdJyqyrqwsulwupqak+25mJuFIUhT179nj8LMIxnCnnvShiRDNSRixSWckAwVm6sFc1V1RU4JJLLsGtt97KOG9s2bIFH/3oR/HTn/5U5Ls+LyRbtmwR5FqyEBbvDz2xeQlFBEgNIzc3d8263HAv5iKre9crkHO9XjCQeg15PwoKClBWVhbSoR9olwqXIr2YaDQajx3qJpMJCwsLTDtzfHw80tPTER8fj8HBQSQkJGDHjh1rDp5wDWfKceLfF1yjGV9LtgibMWLhk8JXq9Woq6tDYWEhrr76anz+85/Hm2++icnJSRHudC0/+tGPcP/996OgoACf/OQncdttt4XsICALYfFFsCLAngkJVMMIR8TCtt2vqqpCXl4ep+sJLSxWqxU6nc7v+xHMtXztUgmlSC8mbPt08tS9sLCA6elpjIyMMJPV8/PzSE9P99sCyq7NsIv9cm9n9gffJphA0UxHR4ffaEaqA15KYWHbFYUKGZCMjY3FNddcI9StBeSrX/0q6urqkJqaijNnzuCb3/wmZmZm8LOf/Syk68laWLjOsnC1ZglHxEJcgQ0Gw7q2+1yuFwputxuTk5Ow2+3Yt2+f35oOV7yFRcgivZhERERArVZjeXkZFRUVSExMxMLCAtMCmpSUxKTM1mtnFmI480LZIMk1mnG73ZsuYhGiI02offd33303fvzjHwf8Oz09PaisrMTtt9/OfG3Xrl2IjIzEF77wBTz44IMhNXHIQlh8ffi4psJWV1fR2tqKuLi4da1ZxBAWtrGl1WpFa2sr1Go1Ghsb/dqVBLoe3/uz2+1oaWmB0+lETEwMb1Eh90UEb6OICokah4eHsXPnTmYYNjk5mWlnJg0AIyMjHu3MaWlpfp84+Q5nStkVJgb+ohmDwQCXywWdThe2uRnChSAsQnSF3XHHHfj0pz8d8O+UlJT4/PrevXvhcrkwOjqKioqKoF9bFsLiCy4iQKxZuO4sUavVgk70s9tYl5aWoNPpkJmZiW3btoX0weIbsSwvL6O1tRWpqanYsmUL+vr6Qr4WG29nYrmLCmmr1uv1aGhoQGJi4pq/Ex0djby8POTl5a1pZ25vb/doAPDXQRfscKbQTthcCaelCzua0ev1qKyshMViCao2wxe++1D4QOorfHz1LBaLIBFLRkZGyO4iZO4v1LUdshUWrVbrVwQoikJfXx+mp6eDsmbRarWCLhAjB8r4+DiGhoZQUVGBgoKCkK/H5/AhzsRbt25FcXExFhcXBa3XuN1uj6cxuYoK8T6z2+3Ys2cPYmJi1v033u3MZrMZCwsL0Ov16O/vR0xMDOd2Zn8NABRFwWazMVFNONuZpfIKA853GmVlZaG4uBgOh4NxCw5Um+GL1BEL39cOt23+2bNn8e677+Kyyy5DQkICzp49i9tuuw2f+tSnkJKSEtI1ZSEsvj70/iIWm82GtrY2uFyuoK1ZhEg1sSE585GREWZWhg+hCAvZJzM2NuaxGEyoKX5yKBkMBsTGxgad3gsnZEFZZGQkGhoaQnaJJhsHidmg0Wj0aGdmuzP7Owy9o5mpqSkmrSD2rhlvpKjt+Np9FBkZybvTjAtSCwvfVmehaixciYqKwp///Gfce++9sNvtKC4uxm233eZRdwkWWQiLL3wJC7FmIUuogv0BCunFxd6yWFNTw1tUgODvj21kuXfvXo8PIxcTyvUgh0N+fj6mp6cxNjbGyb9LCkitLT09nVNrN1e0Wu2adma9Xu+xCIrtzuz9ftA0jdHRUYyOjqKmpgZpaWmc2pnJ/y8EUkQs5LPn73W9azNCRjMbWVhcLhfsdntYLV3q6urwzjvvCHpNWQsLSYWxrVnKy8tRUFAQ0i+KUK7JS0tLaG1tRVpaGsxmsyD7U4DgIhar1YqWlhZERER4GFmGci1fsA++vLw85Ofnw263MwXv0dFRREREePh3STWUtrCwgPb2dhQXFzMLysSA3c5cUlICh8PBDGe2trZCpVJ5NABotVr09fVhbm4ODQ0NjPAHspoRo51ZSmHhet9CRjNStxvzXUsMQPEKEwutVgun0+nRvss33SRExDI5OYmenh6UlZWhsLAQb7zxhmDpNa5isLi4iNbWVmzZssXv0zmfVJi/In1UVNQa/y69Xo/e3l44HA6kpqYiIyMj4AIvoZmYmEB/fz+2b98u2NQwVyIjIxkPKIqisLy8zHSZdXZ2MsNlO3bsCHhQeNdmQm1n9sV6kYNYsFN9wcIlmiFbHH1FM1JO3vOtsZjNZgCKsAiCvxqLyWTCO++8s8aaJVT4tBuzDRzr6uoY51Ix/b18QYRtvUaBUFJhxJ6FS5HeX8Hbe+I9IyMDiYmJgh9sZOvl9PQ06urqBElF8kGtViMlJQUpKSkoKipCS0sLHA4H4uLi0NbWhsjISE7RHd92Zm+kEhYhB2eDjWY2cirMbDYjJiZGUksaIZCFsABrh/CsViv0ej0KCgrWWLOESqjCYrfbodPpfDYMCNkQQITFV+qC3QnHFrZA1yJCweVQ8Z6kD8Ydlr3Aq6ioyCNF1NLSArVavSZFxAdiMLq6uordu3fLao0rmWWKjY1FQ0MD85kj7cx9fX2w2+2MO3NGRobfzjVfKbNgo5mNGLEEgks0ExERgdjYWNjt9rDMzbARIhUWFxcnm9plqMhGWAjEmmV6ehpxcXGoqqoS7NqhCAuZDUlOTmYOCu9rChmxAGtz4k6nEzqdLihnYvZT73ofdKFNJH2liPR6PYaGhtDR0YGUlBQmZRZsW6XD4YBOpwMA7NmzJySTUrEgDQQZGRmorKxk3keNRsMIK5lTYLczx8bGMn+enJwcdDszeYDwFc1IGbGEw7reVzTT29sLk8mEt99+OyxzM2yESIVt9O2RgMyEhW3NUlpairm5OUGvH6ywTE1Nobu7G6WlpX6LwkJHLIBn8ZE4E8fFxQXlTMx186PYk/TsFFF5ebnfQzUjI2Nda3ayeTMxMTGkrkAxMRgMaG9vR1FRUcAGApVKtaadmUR3HR0doCjKYziTazuzv+FMdgNMOJHCgJJEM7GxsUhISEB2dnZQtRkhECIVJqduy1CRjbCsrq4yB2hjYyOWlpYwPT0t6Gtw3UwZzACmGBELuR5xJs7Pzw96L4P3tXwhxSQ92/Kefah677z3NokkreZ5eXkoLS2V1S/ezMwMuru7UVVVFbTZp1arRVZWFrKyspgn7oWFBaadOSEhgXk/AtWqfEUzLpcL09PT0Gq1oCgKDoeD+TmLPZwplWU+8P6DWbjmZrxfWwgDyo2OLISFpml0dHQgJyeHsWYR2tcL8Hy68/fDJ6kWh8OBffv2rftDFvI+yQfb7XZjdHQUAwMDITsTBxKWYIr0YuJ9qHrvvE9KSkJGRgaTHq2srOTsFB0O2H5kZEaFD+z6AWlnJu3d4+PjTMMEqVUFcmcmG0vn5uZQV1eHyMhIUXfNeCOVZT55be/vh2+nGVfcbjev8YMLYd89IBNhUalU2Ldvn8cHUaiZEzbrbaZcWVlhUi11dXWc0k5CdoWR759sOAzWHdnXtfzZ3ZNffClXuLJRqdbuvNfr9RgfH4fFYkFkZCRMJhMMBkNAW5VwQdM0+vv7MTs769ePjC+RkZHIyclBTk6Oz3bm5ORkj/XM5OdIURS6urqwvLy8xtYmXMOZcohYAiFWNKPUWM4jC2EB1qaUQl30FQh2ROAN8doqKSlBSUkJ58NWyIiF+JiZzeaQ3JG98RY9b1GR+nAORGRkJFZWVkBRFHbv3s3sVgnGVkUsyGzV6uoqZz8yvrBrVWVlZbBarUw0MzQ0xLQzp6amYnJyEk6nE7t3717z3oRrOFPqiCWYdBTXaIasaQ70eROqxrLRkY2weH8I2bvIhToAfaXYyJPnxMSEh9cWV4SKWFZWVtDS0gKVSoVt27YJMmDIvreNYncPvN/EQQ5H8l6QbitvW5WEhASmyywhIUHU74106NE0jd27d0vWlRYTE4P8/Hzk5+cz7cxzc3MeByFZaBZI+MQazpR7xBIIf9EM28bHXzQjRI1FERYRIWkooS2w2V1cTqcTbW1tsFqt2LdvX0g/UCG6wmZnZ9HR0YGSkhKMjY0JdjCSdtONJCpkDiQmJgYNDQ1r0pG+bFVIl9no6CizVyUjI0NwmxmbzYaWlhbExsZi586dsulK02g0iI+PR39/P9LS0rB161YYjUbMzc2hr6+PVztzqMOZUtuqCPkwGkw0I0TEohTvRYRdDxHKi4tcl6IojwVh+/btC/k1+NSCiDPx6OgoEy1NTU0J2mXmcrmY+5O7qCwvL0On0yErKwvl5eWcDgfvOoT3ICK7dZdPykosk0shMJvNaGlpQWpqKqqqqqBWq5GYmIiioiI4nU7GnZm0M7PTiP4iLq7tzOTvekczUlr1iylq3tHMysqKRzSjVqsxPz+PyMjIkDrNzGYzUlNTRbn3cCIbYfH+EJKnITEK+MREsaioiHfraqgRC9uZmL1OWajUGvnFXllZQVxcHK/lQ+Fgfn6e2ScTqsko22aGzMzo9XrmyT0uLo5JmflyIvaH0WhEW1sbCgsLUVxcLKv3kQzw5ubm+vwsR0RErOm8W1hYwMTEBLq7u3m1M7OFxrvTTKq1xOT+wiH87IYTEj2/++67cDqdQddmCEoqLAwIXcCnaRpOpxMjIyPYtWuXIKaFocyxEGdirVa7xplYCGEhv/RbtmzB0NAQhoaGmPSQEJYqQjM+Po7BwUHs2LEj5I113rAHEcmTu8FggF6v93AiJikzfxHr7Owsuru7UVFRgdzcXEHuTSgMBgPa2tpQUlKCoqKidf8++yDcunUr7HY7M0dE2pm5WO9wiWYcDgdUKhVcLlfQxpl8kWqDZGRkJFQqFbZu3YqEhIQ10QyXTjOhtkdKjbxOGC+E7LgimwVdLhdKS0sFc8INVgiIM3FWVhaTtuBzPW/Y9ZTS0lKUlpb6tVQJ5FMVDmiaZmzl6+vrkZSUJNprsVfmslt32e8JERrS7jk2NoahoSHs2rUL6enpot1bKMzNzaGzszOkoUxCVFSURxpxaWnJ4z3x187sjXc0s7S0hNHRURQWFnpEM+EazpTahJJ8n97RDJdOM6XdWGD8ORwLkQojOejo6GgkJycLXrPhKn5cnIn5CIu/In1ycjKSk5NRVla2xlKFpIfEciH2h9vtRnt7O6xWa9hadgm+Wnf1ej2z8564y1qtVtTW1krunOzN5OQk+vv7sWvXrpB3mnujVquZAcHy8vI17cxRUVEe65n9FaiXlpag0+lQVlaG/Pz8Ne3MYg9nkmYVKRoryPfn67XXq810dHRgdHQUMzMzogrLAw88gBdeeAE6nQ6RkZFYWlpa83fGx8dx66234tSpU4iPj8eRI0fw4IMPBpXpkI2w+EKIVNj8/Dza29sZWxSdTidoeo1LjSUYZ+JQhIX8MnGxZ2FbqrDTQ2wXYpIyE+uX0263o7W1FVqtFrt37xZU6EMhJiaGeU+I84LJZIJarWY2lpL3RErTSzJNPzY2JrrgebczkwaAnp4eZveOd1OEXq9HR0cHKisrmSjKX21GiF0zvmBHR+GGq6j5imbMZjPeeustjIyM4JZbbsGJEydw4MABXHPNNYLuGXI4HLjpppvQ2NiI3/72t2v+3O124+DBg9iyZQvOnDmDmZkZHD58GBEREfjhD3/I+XVkLSx8UmE0TWN4eBjDw8PYsWMHsrOzeV/T3z0GEgJ2SzMXZ+JgmwG8i6fBdH55p4eWlpaYSIZ0VAm9uMtkMqG1tRUpKSnYtm2brLqryPwMTdO4+OKLERERwRS72TYzRHzDaW/uPekfzjy8RqNholqappndO7Ozs0xTRExMDBYWFrBjxw6/B2E4hjOlFBa2RVIwREZG4vrrr8d1112HqqoqfOc738Hc3Bwee+wxtLa24he/+IVg93jfffcBAJ544gmff/7KK6+gu7sbJ0+eRFZWFmpqanD//ffjG9/4Bu69917OD1ayERZfv6BarTakVBjpuFpeXsbevXs97DaEFpZAQkBScLGxsWhsbORsEcPVidZ7hwqfXybvVAjpqGIv7iKHS6hDiMQBuKCgICh3g3Bgs9nQ2tqK6Oho1NbWMocfu9hts9mY9NDw8DAiIyMZ4Q2UHuIL26Jl9+7dkubgvXfvOJ1OZuGaRqNBT08PM5gZqJ0ZEGc4U6w9MFwg98znc2CxWLB79240NDTg3nvvFWz0gCtnz57Fzp07kZWVxXzt6quvxq233oquri7U1tZyuo5shMUXoYiAxWJBS0sLIiMjsX///jUf7HBFLAsLC9DpdEE7E3NNhYk59OjdUcUeQhwbG4NWq2VEhuuBOjU1hd7eXl7FZrEgqwnWm1GJjo5GXl4e8vLymGl3vV7PpIfIfEhGRoZgNjNutxttbW2w2+0+LVqkZnp6mmm+SE5OZiK88fFxdHd3IzExkRGZQA8kQg1nSmlXJMTvotls9ohGw/19zM7OeogKAOa/Z2dnOV9H9sISTMRC7NdzcnL8bp3UaDSMJ5cQeEcsxPF2YGAA27ZtC7pFlUsqjP2LFo6hR19DiORAdTqdTMosIyNjjZATZ+KJiQnU1tbKbviL2PEHG0V5L+8ymUxYWFjwMDHkG+E5nU6mNbqhoUHyWhQbkmqemJhAfX09kxXwbmcmEd7o6KjHe8a3nZn9d71FScqOMD7RisPhgNPpDDrNeffdd+PHP/5xwL/T09ODysrKkO8tWGQjLP66wrhEFzRNY3R0FIODg+se5kJYsHjfI4kwKIpCd3c39Hp9yM7E60Us7Kc4KSbp2UOIFRUVa3y7EhMTPVqZu7u7GadmuQ1+zc3Noauri/eMCttmhth+eNvdk5QZ16YIudrHAGvrPf5+rlFRUcjNzUVubq7PdmbS4k02iQY7nMlOnZG/53Q6JW81DhWz2QwAQVu63HHHHfj0pz8d8O+UlJRwutaWLVvw3nvveXyNLFwMpolANsLiC61WC6vVGvDvkP3nRqMRe/bsWXcWQowaC0VRsNvtTMcZH2dif8LCp0gvFt6+XeQJVa/XY3h4GMD5n2FFRYXsevPJUObOnTsFa9kleEd4pCliYGBgzWpmX23Wvixa5AJN0+jp6YHBYAiq3uOrhkfEd3BwkGlnzsjIQHJysl8hXa8BwGw2ewhMONNifCMWk8kEIHhhIQ9yQtDY2IgHHngA8/PzzLDyq6++isTERGzbto3zdWQlLOwd3cD6IkAMC9VqNfbv388p/yxGjYWmaZw9exbJycm8ny592dgIWaQXE/KEmpKSwtS54uLi0N/fj56eHqZt13s7ZDihaZopNos9lAl4HqgVFRVMRxXbZoa9mplsUvVn0SIlFEWhs7MTJpPJw3U6FNht76SdWa/Xo6urC06n08PPLNDrsKMZvV6PwcFBlJaWAoDPlBn5/8VAqO2RYv5+j4+Pw2g0Ynx8HG63GzqdDgBQWlqK+Ph4XHXVVdi2bRtuvvlm/OQnP8Hs7Cy+853v4Etf+lJQ9T1ZCYs3gWosBoMBOp0OW7ZsCeqpTshVwsD5ug4AwQ4C7/vjOp8iF8iAXE5ODsrKypiHhdXVVY/tkGSqm7TthgPvBVhSRFHsffdkjog0epCfc1ZWFoqKimT1syYDrXa7HQ0NDYLO83i3M5N6FelIZIuvPysUMkNTVVXFjBaItWvGH0It+RLz5/69730PTz75JPPfpMvr1KlTuPTSS6HRaPC3v/0Nt956KxobGxEXF4cjR47g+9//flCvI2th8TUgSdM0xsfH0d/fj8rKSuTn5wd1TaEiFlKUHhkZAYCQjRO9YafCNpLdPfC+rxaZuiawrcdJ265er2eeMGNiYpiDJSkpSZQnNjKj4nK5sGfPHkkHHQnsOaLZ2Vl0dnYiLS0NJpMJb7zxhof4in3gBMLlcqGtrQ1utxv19fWiRpve9Spv8QWAtLQ0rEauYlG1iIqMCiQ6E9HR0YEdO3Z4dDSJ0c4ciI1gmf/EE0/4nWEhFBYW4sUXX+T1OrISlvVSYW63G93d3VhYWEBDQ0NIk8dCCIvb7UZHRweWlpawd+9enDlzRrD0mkqlWvNLIHdRIc0TIyMjnGoW0dHRzFS3y+Vi0iBtbW0AILhhJplRiYqKQn19vexMONkWLSSvTSxViMdbVFSUx8xMuNKhpDNNo9FwXtctJGzxpWkao3Oj+PzLn8eZ+TPM36lNqMWvPvSrgAam3iIDQJThTL41lnAO3YqJvH7DvGAPSJLDAQCv4jhfYSF1HY1Gw8zJCJleI11r7CleOX/QKIpCb28v9Hp9SLvftVotMjMzkZmZCZqmBTfMJJP+ci2Ej46OYnR0dI1Fi7elCnlq7+zs5LxThS8OhwPNzc2IiYmRRWeaSqXC1976Gt7Vv+vx9bbVNnzx9S/ihzM/ZN6T1NTUdduZhY5mhEiFya1zMlRkLSxEBIgjcEZGBrZt28brA85HWMh9ZGZmetiRCGF1D7y/znV5eRl9fX3IzMxESkqKbIWFOEbb7Xbs3buXt+2LSqXiZJjJdZ9KqDMq4SAYixaNRuMhvqReRXaqkBbv9PR0xMfHC/J92mw2NDc3IzExEdu3b5eFIA8YB3By9OSar1Og0LrSiti8WGicGgwMDMBqtTIPJWlpaQFTTP7amYn3F9doRohUmNy6J0NFVsLia++9w+HAuXPnUF5eLkgdI1RhmZqaQnd3t8/7ECK9Ror0GRkZ0Gq10Ov1zNMpSQ2lp6fLJo3DTi/t3r1blPvyZ5hJOgEDGWaSGZXy8nLk5eUJfm98IPNOZL4nmMPEu17FHkIcGRlBRESEx1N7KAedxWJBc3Mz0tLSUFVVJRtBHl4aDvjnBhjQUNGAioqKNQ8lMTExHu7M/oSSz3CmEMKiRCwiQ1EUhoeHQdP0uo7AwUDSVlxXp5KdIVNTU6itrfW5l4NvxMIu0nt3yKysrDBzIZ2dnR5T7kIZQwbLysoKdDpdWNf0+jLMJDb3HR0djNtuRkYG5ufnRZtR4QvprrLZbIJYtHgPIRJXhN7eXsaFOBgjUZPJhObmZmRnZzNdfXKhJDnwkN/WlK3M/89+KCF1vIWFBXR1dcHlcnm4M3NtZ15vONPtdvNKSyrCIjLEVp2EoKFMsPuDPFG43e51n7LZzsT79u3zG07ziVgCFenZ9tqlpaVrVu0S25DMzEzBUiDrQdo6i4uLJWuJ9R62M5vN0Ov1mJ2dRW9vLwAgJycHUVFRku5e90ZsixZvVwTyvrCNRNltu97vC1lzXFBQILsVzAAQZYpCXWId2lbb4Kbf/33TqDS4rPAylKaU+vx33nU8X/Y7RGQCpVgDDWe63W6YTCakpaWFPJxpsVgUYRGL5eVlZup427ZteO211+ByuQQrHHIVFjL9HBMTg3379gU8BEKJWEj+NpgifWxsLAoLC1FYWLjGGDIiIsLDGFKMKGJiYoLxQBNyRwRfiG27yWSCzWZDXl4eVldXce7cOcYwk09qSAhI6jBchXC2CzGxmfG1e4fYzKyurkKn06GkpASFhYWi3lsokK7DP374j/jqG1/1qLVcVngZnrj+CU7X8WW/46udmbw36/3ek9/97u5uAGCsgUIZziRdYRcCshKWmZkZtLW1obS0lNnhrVKpBJ2UJ6tRA12TDF/m5uaioqJi3QM/2IjFe5Ke3FMwsG1D2JPLYtRl2NPqdXV1gkaQQkDmLJxOJ/bs2cOkl3ylhtjT/+FyCpaDRUtkZCSys7ORnZ3t07eLpmlkZWXJLnUIgFluRswun73pWQwuDmJocQhbU7b6jVS4wH5fSFciMc1k79/x1xhBURTTwMKe8QllONNsNgdsmd5IyEpYYmNj19QxhLZgIT9QX9dkD18G40wcjLGlGEOPYtZliBebyWSSbFo9ECRtGhkZiYaGBg8R9WWYubCw4NMwU6z5gZWVFbS0tHg4EUgNO5WYlJSEjo4OZGdnw26348yZM4iNjfXovpOqI8zbQZndOVeaUspLUHzB7kosLS312L8zMjICrVbrEeWpVCp0dHTAZrOtGRwNZThTqbGIRHJy8hoLl1CXfQVCq9WuSV2RcHZ+fj7o4UuucyzhmKQXsi5D1vSqVCrs3r1bFtPqbEgkwGUbpXcKxNswkyztEjKVaDQa0dbWxtSj5AapMVRXVzORisvlYlJDbW1toGmaOUzD6fFG0zQGBwcxPT0d0EFZTNj7d9jRL9mwqtVqoVKpUFNTwyllBgTeNTMzMyP5rJBQyEpY+FjnB4O30aPD4UBrayvjTBzsIB6XGotUk/Sh1mXMZjNaW1uZOQa5feCJJ1leXh62bt0a9PvJ7qbyNkF0u928DTPn5+fR2dnJ25JfLIi7c01NjceOHK1Wi6ysLGRlZXlEv+zUEHlfxIrySOqVzPjIoe7Ajn4pioJOp8Pq6ipiYmLw3nvv8W5nPnPmDFpaWvDBD34wbN+TmMhKWHwR7LIvrtckQkDcZJOSkrBjx46Q6hGBxC+UIr1YcK3LaDQadHZ2Ij8/P6RDW2zIoe3tSRYq3qlEb8NMcphyNcwkFi07duyQZc58ZGQEo6Oj67o7e0e/xOON1GbYVvdCRXmkvZ84Ocgt9Uocnu12OxobGxEZGelhS9TZ2ck8mBChCVTLU6vVOHfuHD7+8Y/jv//7v/GlL30pjN+NeKhorgvWwwBFUXA6nR5fe++995gnS6E4e/Ys007Z3t6O4uJiXgdod3c3NBoNKioqPL7uvUMllCJ9OGA/mU5PT8NutyMuLg75+fmSzsv4gnSmhevQZhtmLi4uIjo62q9hJtuipaamJiQvOzFhp5fq6uqC3lTIhv1gsrCwAJfLxfkwDXR/ZNdLQ0NDSBY+YkJExWw2o76+3mdqmDyYkNrMyspKwDbvlpYWXH/99fjud7+L2267TZbnQyjISlhomobD4fD4WnNzMzIyMlBQUCDY67z77ruIjIzEwsICdu7cybt1tq+vD26322MRDrueItUO7mAghdLx8XFUVFTA6XRCr9djaWlJknkZX/c3NDSEyclJ1NTUSNKZxn4y1ev1AOBRzB0eHsbs7CzvQ1sMaJpGb28vFhYWUFdXJ2h6icyGEJFZWVlBQkICkzLjspqZpml0d3djcXER9fX1shMVmqbR2dmJ1dXVoNYGsNu8DQYDVCoVhoeHQVEUysrK8O///u+466678I1vfOOCERVgE6bC3G43LBYLTCYT9u7dG7Rpoi/IxjrCRrO7J40Li4uLHiuEpZqXWe/+pMq5+zPMHB4eRkdHB9RqNQoLC2Vju0NgW8iIEQn42iRKDtPR0VGmmyojI8PnLBG5v+XlZTQ0NMgqQgbO/z53dXUFLSrA2jbv5eVlNDc34/HHH8fk5CQzM9TV1YXt27fL/qzgiuwjls7OTkRFRaGsrIz39ckecavVioKCAkGuCQDDw8NYXV1FdXX1hhMV4i7gcrlQW1sbMIXBTn/o9fqw+JgRo0uHw7Hu/UkBsWixWCzIzs7G4uIiFhcX12yGlOpzQFEUOjo6YLFYUFdXF/b3j3RTkYcTu93uYb8TGRnJtLPX19fL7udLRGVlZUWw++vr68OBAwfw0Y9+FDt27MCLL76I1157Dd/+9rfxrW99S4C7lh5ZCQtwfi6BTU9PDwCgqqqK13WXlpbQ2tqK9PR00DSN2NhYZoUpX0ZHR2E0GlFTU7NhdqgA768AINPgwQgDuy4zPz8Pi8UiuI8ZmVGJiIhAdXW17CIBp9PJTGuzW07ZhpkLCwvrGmaKhdvtZgZH6+rqJFsHTaBpmml/1+v1WF5ehkajgUqlwo4dO5jZELlA0nMk0hNCVAYHB3HgwAF84hOfwE9+8hMm4rfZbLDZbLIbPg4V2QtLf38/HA4HduzYEfI1p6en0dXVhbKyMhQWFqKnp8dnsT1UxsfHMTc3h5qaGgDyLdKzWV5ehk6nQ1ZWFid3gfVgHxhC1GWCmVGRAmLREh0djV27dvkVC5L+IO+NzWbz2DEjVtqHiB6Zs5CbKFMUhdbWVlgsFiQmJsJoNEKlUgm+5C1UxBCV0dFRXHPNNWhqasLPf/5z2X2mhUR2wuJwODy2SLLTTMFCdl5MTEygpqaGmej3VWwPFZqmGeEi+ff09HTZzX2wIe26paWlgjZFENh1GYPBEHRdhsyo5ObmorS0VHYizUf0iDEkeWInApyRkcGpyM0Fh8OBlpYWREZGorq6WnafRV+RFBFg8rmxWCxISUnxWM0cLkh3GmkkEEL8JyYmcPXVV+Oaa67Br371qwtaVIANICxjY2MwGAyoq6sL6jrEP4q0BrILvgMDA7DZbNi5cyeveyX1FOJsStJCNpuNGbAjeWQ5QCxrhoaGwtauG2xdRugZFaFZWVlBa2urILby7I6hhYWFdYvcXCB1xPj4eOzYsUN2B5jb7YZOp4Pb7UZtba3f9BzZp7KwsACj0YiYmBjmM5OcnCza90VExWg0CtZIMDMzg6uvvhqXXHIJHn/8cdkJvRjIXlgmJycxPT2NPXv2cL6GxWJBS0sLoqOjUV1dvebDOzIyguXlZSZ1FQpsI0nv1JfZbMb8/Dzm5+exurqKpKQkJpqRqo2Soij09/czKbtAg3FisV5dZmFhQdaDhWJatLAtQ/R6fUiGmVarFc3NzUwkJbdIz+VyQafTgaZp1NbWck51sdu8FxYWmNXMJGUm1IMbackmczRCiMrc3BwOHDiA3bt344knntgUogLIUFicTqeHPcrs7CxGRkbQ2NjI6d8TZ+KcnBxUVFT4fLIZGxvDwsIC6uvrg74/MknPtUjPHrAzGo2Ii4tjRCZcMyEulwsdHR2wWq2ora2VzYwAqcvMz89jaWkJwPk9KgUFBZLNy/gjnBYtNE17pMxWVlbWXT9sNpvR3NyMzMxMQWpmQuNyuZjNnzU1NSEfsOThhKTMTCYT40DMx0xUDFHR6/U4ePAgduzYgT/+8Y+yq3OJieyFRa/Xo6+vDxdffPG6/3Z8fBx9fX2oqqoKuI52cnISMzMz2L17d1D3xneS3ul0Mr8QCwsLiIiIQGZmJjIyMkQL7202G3Q6HSIiIrBr1y7JO4O8oSiKmbbOz8/HyspKSHUZMZmamkJfX59kkRTbMNNgMKwxzDSZTGhpaQnZN01syIIzrVYreM2H7UDMfm+IZxeX1/K2kRHiwctoNOLaa69FaWkp/vKXv8ju905sZC8si4uLaGtrw6WXXur335DDaW5uDrW1tetaaczMzGB0dJRzFASs3aHC96Dzrj3QNM0cFkK1pJLlTXLtrCIzKna7HbW1tcxTohTzMr5gW7RUV1d7mDVKBXlviNC4XC5QFIXMzExUVlbKpp5HcDqdaG5uRlRUVMDuOSFwu91MOnFhYQFOp9NjNbOvdKIYorK0tITrr78eOTk5OHbsmOx+JuFAdsLicrk8DB1XVlbwz3/+Ex/60Id8/n1i7U46TLh8MObn5zEwMICLLrqI0z2JPfRIprhJXcZutzMHaUZGRkhPOwaDAe3t7SgsLJTlmln2jEqgSCoc8zL+XndgYAAzMzOora0VxKFBaIi1fUpKChwOB5MWCsYwU0wcDgeam5sRGxuLnTt3hvXBhr2CmKQTExISmN8rYrnT39/PrMoQQlRWVlZw6NAhpKSk4MSJE7JzEQgXshcWs9mMf/zjH7j66qvX/F3iTJyYmBjUgJ/BYEBXVxcni+pwT9KT/DoRGZPJhOTkZCZlxuXDPzk5ib6+Pmzbtg3Z2dmi3m8oEEv+pKQkbN++Pei94ELOy/iCbYFSV1cnO4dd4P2aT1VVFfMzZrsPG41GxjBT7E4qX9jtdjQ3N8umO420wJOUmVqtRkREBBwOx5olYqFiMplw4403IioqCn/7299kU8uUAtkLi91ux6lTp3DVVVd5fDjn5+eZDp1g88pkCv+yyy4L+Pek2qHCxmq1erjrxsfHM8V/70Ilca+dnJyUTerGm+XlZbS2tgqyUZHvvIwviEWLzWaTxAKFCzMzM+ju7sbOnTv91nzcbrdHKzM7nZiWliZqzp+0PCckJAT94BAOyFZU8plxOBzM0Gp6enpIgmCxWPCRj3wEAPDCCy9cMJsgQ0V2wuJ2uz1MJ10uF06ePIkPfehDiIiIAE3TGBkZwdDQUMjOxKurq3j33XdxxRVX+PxzudrdE8dhclhERUUxkUxCQgK6u7uxsrKC2tpaydMgvtDr9ejo6BBlMFOIuow/ixY5QXa9VFdXIy0tjdO/Ye9y1+v1MJvNHgepkBGZzWbDuXPnZNvyTB6+ZmZmmH0vZrOZeW+WlpaC9nmzWq342Mc+BovFgpdeekmWadNwI3thoWkaL7/8Mi655BLGsG5xcRF1dXUh/wAtFgveeustn+k1oYv0YsF+Ip2fn4fb7YZWq0VFRQUyMzNl1y9PDsTt27cjKytL1NcKpS5jt9uZ2Sexi8yhMjo6ipGREdTW1vLylPKOgsmOe76GmWSOJjU1FVVVVbIVFbLu2NfDF/F5I2kzwHM1gvfDht1uxyc/+UkYDAa88sorF4zXF19kLywA8Oqrr6Kurg79/f1QqVS8XW79pddIpOJ2uzeEiSTwvr1IdHQ0EhISoNfr4XQ6kZaWxtjLSPnkzd7zItXyq/XqMlarFS0tLUhOTpZl9xx7Fw2fBypf+DpIicikpqZyrltaLBZmd5Ic52jIezg1NcV53TF7NcLCwgLMZjOSk5NBURSioqJQVVWFw4cPY2JiAq+99posU89SITth8bVF8uTJk1CpVMjMzBQkZ+udXgM23g4V4H1PLXa9gnTDzM/PMwNkKSkpTMosnF0q7BmVuro6WeSdvesyZN9Peno6duzYIbtIhfjdzc3Nif4ehmqYSYYzs7KyUF5eLsvfncHBwaBExRdWqxULCwv44x//iB//+MfQarWIjY3F448/juuuu25TthX7Q/bCMj09jfb2duTn5wuWs6UoCq+88gouvfRSREdHy6JIHyyzs7Po7u5e11OLpD3IdHtCQgIjMqFOKXOBXQRnz6jICdKuGx8fD5vNJtm8jD+k3qrIxTDTZDKhubkZOTk5sjQMBcBEe/X19YIIs8vlwmc+8xm88847uOyyy/Daa6/BYrHgS1/6En74wx8KcMcbH9l6DLCdiYkBnVAfWiIeZLiMdKFtBFEhQ3sjIyPYtWsX49jsj5iYGBQUFKCgoIB5Wp+fn8fw8DCio6MZkRFyGZXD4UBrays0Gg0aGhpkWQQn7bqVlZXIzc31qMsMDw+js7MzLPMy/iD71U0mk2RbFePi4hAXF4eioiIP14jx8XFoNBokJSXBYDAgLy9v04iK2+3GV77yFXR0dOCf//wncnJymBUAKysrAtzxhYEsIxar1co4E9fV1aGzsxMFBQXIyckR7HVOnjzp8WGTS+dXINippdraWl6996T4T1JmarWaqTukpqaGnG4kBqCJiYmymF/wBReLlnDMy/iDRHt2ux11dXWyS7FQFIWpqSn09/dDrVZ7mEJyNcwMB6S219DQIIioUBSFr33tazh16hROnTolysqJCwXZCYvZbMY777yDqKgopuXz3LlzyMrKEsxGnaZpnD59Glu2bEF+fv6GGGRyOp0eK3qFfIKlKApLS0uMyDidTqSnpzPFf64pISFnVMSCRHvBzPmIMS/jD7YDsFxbnpeXl9HS0oLi4mIUFhb6NMwkKUWpzERHRkYwNjYm2PAjRVG466678OKLL+LUqVMoLi4W4C658eabb+Khhx5Cc3MzZmZm8Mwzz6CpqYn5809/+tN48sknPf7N1VdfjZdeeils9+iN7FJhWq0W2dnZKC4uZn5hNRqNx9AkH0iRvry8HFNTU3j77beZugMZOpQbVqsVOp0OUVFR2L17t+C5f7VajdTUVKSmpqKiogKrq6uYn5/HyMgIkxIiKTN/T6NkRmXr1q0oLCwU9P6EgG3RUl9fH1RnVWRkJHJycpCTk+MxL9PZ2SloXcbpdKKlpQVarRa1tbWyayQA3h8uLikpYX7O8fHxiI+PR3FxMRwOByMyIyMjawwzwxHBiiEq3/72t/Hcc8/h9OnTYRUV4PzDdnV1NT772c/ixhtv9Pl3rrnmGvz+979n/lvqqFF2EQtN03A4HB5fa29vF2RHva8iPbvuYDAYEBMTw4iMUBv9+EAWS2VkZKCysjLsqSWLxcJEMsvLy0hMTPQo/gPnU0u9vb3Yvn17SAOrYkMsWsj8k1APD0L6mJE5mpiYGOzatUuWKcTFxUW0trZyXsLmyzCTzISkp6eLkuIjpqFCiQpN07jvvvvwhz/8AadOnUJlZaUAdxk6KpXKZ8SytLSEEydOSHZf3shOWIC1e++7u7t57agnO1RI1OOvnuJyuTzqDsTWPjMzE8nJyWEXGRIFkKdDqUWO2LfPz88zW/0iIiKY1dFcJ8HDSTi700Kty5A5mqSkJFnO0QDnbeB1Oh3Ky8sDrqTwB03TWF1dZd4fMQwzSZoz2Ig00D0/+OCDePzxx3Hq1Cls376d9zX54k9YTpw4gcjISKSkpODyyy/HD37wA0l/HzeEsPDZUe89Sc+1SE9RlMdku0qlEqS4zZWJiQkMDAyEZVI9FBwOBzo6OrC0tASVSgWtVsu8P1LvTyFIadHCtS5DBlzT09NRWVkp+cODLwwGA9ra2lBZWSlYAw3Zo0IW4EVHRzMpxVAMM8fGxjA8PCyoqPzXf/0XfvGLX+D1119HdXU172sKgS9h+fOf/4zY2FgUFxdjaGgI3/rWtxAfH4+zZ89Klk6VpbB4ryceHByExWLBrl27groOe+hRpVKFfNh5F7dJSE+K20L+8Eib9czMDGpqamRpEUGiAKvVynQtLS4uMu+P2+1m3p+0tDRJ5kHkZNHiz8csISEBo6OjyM3NlW27Loma2S7KQsPXMHN8fBxDQ0OCisrDDz+Mhx56CK+88goaGhp4X1MofAmLN8PDw9i6dSszBC4FG0JYRkdHsbi4iNraWs7XEGuSnuTVia29zWZj7FNC3Z1CIK6rJpMJtbW1srRrJ/tv1Go1qqur13y/3nUHq9XK1B0yMzPD0jpLWp7laNFC3p+JiQnMzMwAgEdzhJwGSefn59HR0YEdO3aELWpmf364GGYSUamrq0NSUpIgr//rX/8a999/P1566SXs27eP9zWFhIuwAOdteX7wgx/gC1/4QnhuzAvZdYX5gthucEVMexaVSoWkpCQkJSWhrKyMsU8ZHx9Hd3c3Y5+SmZkZVGeG3W5nDuzdu3fLbnYBOH9gt7a2MnbovqIA9vtTWlrKtKJOT0+jt7eXyatnZmaKIpxkR8+WLVtkaS+iUqngdrsxPz+PiooKpKenQ6/XY25uDn19fWGdlwnE3NwcOjs7A1rzi4H354dtmNnf3+9hmLmysiK4qPzud7/DfffdhxdeeEF2osKVyclJGAwGSXcxyTJi8V5PPD09jfHx8XV/0FyL9GJhtVqZSIbdQbXeIWoymdDa2ork5GRZ7q8A3u9O43Ng2+12JpIxGo2Ii4tjDlEhOvAWFxeh0+lQVFSEoqIi2YkK8H5qqaKiArm5uR5/Fs55mUDMzMygp6cHO3fuREZGRlhekwukuYYIMUmZ5eTk8E650jSNP/zhD7jzzjvx/PPPB1yFHm5MJhMGBwcBALW1tfjZz36Gyy67jBkRuO+++/CRj3wEW7ZswdDQEO666y6srq6io6NDsrbjDSEsXFYJy22Hiq9DlIgM+0nUaDSira0N+fn5QS8sCxcLCwtob29HSUkJioqKBLmmy+ViOswWFhaYQ5R04AV7iBKLllC7lsLB7Owsurq6OKWWhNgvEwoksuRiFyQVZAVDWVkZE9FwNcz0BU3T+Mtf/oKvfvWrOH78OK666ioR7z54Tp8+7XMp4ZEjR/DYY4+hqakJra2tWFpaQk5ODq666ircf//9kjb9bAhhWW+VsNx3qBCfJXKIRkVFISsrCyqVCqOjo6iqqlrz9CoXpqen0dPTI+qaY4qiYDQameI/RVGMyKSlpa1beCeHYSCLFqkhNjKhHNhCzssEghzYNTU1srWAJ/dYV1fn0dhCUq4LCwserd7p6elITEwM+MB2/PhxfOELX8DTTz+NgwcPhuG7uPCRpbB4rydeXl5Gc3MzLr/88jV/d6PZ3bvdbiwsLGB4eBgmkwkRERHYsmVLyE/qYkHMLkdHR8O65pi9A8O7OcLXUF0oFi3hhhSYhbpHMXzMSHt7bW2tJDtzuEDEeb17ZBtmktUIJNpLTU31eFB5/vnn8dnPfhZ/+tOf1i2IK3BnQwiLyWTC2bNnceWVV3r8vY0mKoDnFHh1dTWcTidTl6FpmnkK5fKkLhY0TaO3txd6vZ632SXf+yBPovPz81hdXUVycjLzHk1OTmJ6elrw5VdCQdZoj4+Po7a2VpACszdC1GWI8PHdTCkmXEXFG4qisLi4yLxHdrudsXFKS0vDHXfcgSeeeAI33XSTiHe/+dgwXWEk1UWWWbFrKhtFVJxOJ9ra2uB2u7Fnzx6msJaWlobKykosLy9jfn4e/f39cDgcIRlB8sXtdqOjowMWiwW7d++W1KBTpVJ5+FDZbDZGZMg20by8POYzIafPgLc3mVjizNfHjER8QnVWicH09DT6+vpC2kCqVquRlpaGtLQ0lJeXMwOpv/nNbzA2NoaSkhL09fWhra0Nu3btktVnaCMjy4jFez2x0+nEa6+9hiuuuAIajUZWRXquWCwW6HQ6xMbGYufOnQGjEfYWyPn5eZjNZo9ZGbFakcmMikqlkq2zLlv48vLysLi4yDypS2m/w4amaY/NmVIYm3KpyxBbeblGfMD79TMh6z5vvvkmbrrpJjzwwAOIj4/H3/72N7zyyiu47777cMcddwjyGpudDSEs7I2PWq1WtkV6fxA7+ezs7JBadYkR5Pz8PFZWVpCcnMwcokIVbolfVXx8vCxX9ALvW7TQNI3a2lpG+NhP6vPz8wAgWUqRoih0dXVhZWUF9fX1shl49K7LREREwOVyMZZBcnw4I23PQorK22+/jY985CP46U9/iltuuYX5vm02GxwOh2wFdqOxIYQFAF5++WU0NjYiOjp6w6S+gPODZl1dXSgtLRVkMRA7HbS4uCiI5T+ZUcnKykJFRYUs31uuFi00TWNpaYl5j+x2O5MO4uuMsB4kmiJWN1Jbl/uCpmn09fVhenoaSUlJWF5elmxeJhBEVIQ0N3333XfR1NSEBx54AF/60pfC9jlfb58KTdO455578Jvf/AZLS0u46KKL8Nhjj6GsrCws9ycGsqyxeP/AaZpGREQEBgcHmWEoOR5+bGiaZozxhGyDjY6ORn5+PvLz89esGg7F8t9gMKC9vZ1Z2iTH9zUYixaVSoWUlBSkpKSgrKwMZrN5jTMC6aASMppwu93Q6XRwu92yXcdMfOjm5+exd+9exMXFibpfJlRmZ2cFF5WWlhbceOONuPfee8MqKsD6+1R+8pOf4OGHH8aTTz6J4uJifPe738XVV1+N7u5u2US8wSLLiIWiKDidTub/pygKy8vLmJ2dZUwgg5lzCDcURaGvrw/z8/OoqakJS1E0FMv/cMyo8EVIixa2Pcji4iLi4+M9or1Qr+10OtHa2gq1Wo2amhpJDuP1IJGKXq9HfX29TyeIcM3LBGJ2dhbd3d2CDmi2tbXh4MGD+MY3voG77rpL0ocnb68vmqaRk5ODO+64A1//+tcBnE+dZ2Vl4YknnsDHP/5xye6VD7IVFofD4bNIzzaBnJubk6x7yh8ul4tJh9TW1krSVbWe5T8ZzBwdHcWuXbtkuUcFENeixel0MiJDhlaJyCQlJXF+LYfDgZaWFkRFRUnuouwPdjNBQ0MD58+kGPMygSBpYyFFpaurCwcOHMBXv/pVfPe735U8IvcWFuJE3NraipqaGubvXXLJJaipqcEvfvELaW6UJ/J7tML5g9HfJL23SZ3JZMLc3ByGh4fR1dUlmNNwKNhsNuh0OkRERGD37t2SpUPUajXzlFlZWcnUHHp6euB0OhEVFQWHwyHruQXiqSWWRUtERIRHmy4RYtIVR94/74E6NjabzaPhQQ61CW9ommbmpnbv3h1U1BEbG4vCwkIUFhZ6zMuMjY0JXpchorJz507BRKW3txfXXXcd/t//+3+yEBVfzM7OAsAa+5WsrCzmzzYishSWW265BaOjo7jhhhtw6NAhv10rKpUKCQkJSEhIYESGnU9PTU1FVlaWqC26hNXVVbS2tiItLQ1VVVWyOWTY++y3bt0KnU7HTPw3NzdLKsT+ICm6cNm1azQaJlohu3f0ej16e3vhdDo9ag7kPbJYLMz7V1VVJctDi92h1tDQwCuVxXdeJhDE523Xrl2CmV4ODAzguuuuw+HDh/H9739flj+fCxlZpsLGxsbw17/+FcePH8c///lP7Nu3jxGZ3NxcTh8S0qI7NzeH1dXVkO3subCwsICOjg4UFhaiuLhYlh9iX6267FkZk8kk6nvEFTlZtPiaJ0pNTUViYiImJydDbh8PBxRFMbt96uvrRft58q3LkJ0vQtrzj4yM4JprrsGNN96I//7v/5bNQx6weVJhshQWAk3TmJycxPHjx3H8+HGcOXMGdXV1aGpqwqFDhzh3MdlsNkZklpeXkZSUhKysLEE6gyYnJ9HX1yfrArjVakVra2vA4cxQLf+FgqZpDA4OYmpqSrYDe1arFePj45iYmABN0x7vkRRDkP6gKIoZIq2vrw/rbp9g6jJ6vR7t7e2Cisr4+DiuueYaHDhwAI8++qisRAXwX7z/+te/zgxnrqysIDMzUynehwOapjE7O4tnnnkGx48fxxtvvIGdO3cyIsN1tavdbmcOUDIHQkQmmAOUHISTk5MhWU2EC9JVlZmZyXmnOlfLf6GgKAo9PT0wGo2STapzgTQTFBcXIycnhzlADQYDoqOjmZRiMMV/oaEoCm1tbbDb7czaaKkI5GNGZn6EFJXp6Wlcc801uPTSS/E///M/smmkCLRPpaCgAD/+8Y/xox/9yKPduL29XWk3Djc0TcNgMODZZ5/F0aNH8frrr6OiogKHDh3CoUOHOOe8HQ4HszTIaDR6tJ/Gx8f7/Xdut5vJXdfW1sr2ICQzKny6qnxZ/ofSPeUPtkVLXV2dbH+RDAYD2trafDYTEMdqIjSkeYJ04YXrqdntdqOtrQ1OpxN1dXWyqZkBnu4Ic3NzcLlcSE5ORn5+viDdnLOzszhw4AD27t2L3//+97IRFSDwPpUnnniCGZB8/PHHsbS0hIsvvhi/+tWvUF5eLsHdCsOGFBY2ZNL6ueeew7Fjx/Dqq6+iqKgIhw4dQlNTE+duHdJ+Oj8/D4PBwAwbZmVleTylEz8tAKipqZHlCmHg/ORyd3c3qqqqkJOTI8g1SfcUmZUhRe9QO4P8WbTIDVIH4JLuJMV/8h65XC4P23+x2uHZA5p1dXWSt937Y2FhAW1tbSgpKWFWNPOdl9Hr9bj22muxc+dO/PGPf5Tt976Z2PDC4s3Kygr+9re/4dixY3jppZeQnZ2NG264AR/+8IdRW1vL6fDz3m4YGRmJrKwsJCQkYGBgAElJSX53vksNe+JfyMllb4gdeaiW/3a7Ha2trYiMjER1dbUs30uA3xIxmqaxurrKiAwp/hMxFqqg7nK5PARargcr2US6bds2bNmyhfk6n3kZg8GAgwcPorS0FH/5y19k+3Cy2bjghIWNyWTC3//+dxw7dgwvvvgiUlNTcf311+PDH/4wdu/ezekwI0/pExMTMBqN0Gg0yMnJQVZWluQuut6Q6eq5uTnU1taGrQBOlnMRkSFDq0RovA+6YCxapIQsvxJKoEmnol6v92iQyMjICDmd6nK5PKb+5SrQJJVYVVUVMOoLZr/M0tISrrvuOuTm5uLYsWOyzR5sRi5oYWFjtVrx8ssv4/jx43j++ecRGxuLG264AU1NTWhsbAz4lEcM8UpLSxETE8McDiqVikmXSb390e12M+2ldXV1ku1R8Wf5T55AiZmkEBYtYjIyMoLR0VHRhkhJgwQ5QGNjYxmRWW+VLsHpdKKlpQURERGyjvq4ioo37LoMWVm9tLQEg8GAK6+8EkeOHEFqaipOnDgh29rcZmXTCAsbm82G1157DcePH8ezzz4LjUbDRDIXX3wxE05TFIXe3l7Mzc2tmQj2lQoiRe1wFmwBz1qF3Oo+bMv/5eVlAEB6ejoqKyslXSLmD5qmMTQ0hMnJSVEXdLFh+7wtLCxwql05nU40NzfL2koGAIxGI3Q6XdCi4g2Zlzl27BgeeughTExMICUlBd/97nfxkY98BPn5+QLetQJfNqWwsHE6nXjjjTdw9OhRnDhxAi6XC9dddx2uvfZa/OEPf8DKygqefvrpgAcMaSCYm5vD/Pw83G532Ewyia0IlwViUkJmFjIyMuB0OgWz/BcSkkqcn59HfX29JPfEfmDR6/Vwu92MF15aWhq0Wi0cDgeam5uZn7lcU4lEVCorKwVrILFYLPjIRz4Cs9mMj370o3jppZfwj3/8AzfccAOOHz8uyGso8GfTCwsbl8uFf/zjH/jTn/6Ep556Ci6XCwcPHsSnPvUpfOhDH+L0hE2erIjIkHpDVlYW0tPTBT34iY0MiQDkesD4smhhW/6zu/CCsfwXEjJLs7i4iPr6ellEU2zDVb1eD6vViuTkZJhMJiQlJWHXrl2y/ZmLISpWqxX/9m//BqvVipdeeompIRoMBgwPD2P37t2CvI4CfxRh8WJsbAwHDx5Efn4+br/9drz44os4ceIEDAYDrr76ajQ1NeGqq67i9DTL7gqan5+H1Wr1cGLm08FiNBrR1tYmaxsZgJtFSyiW/0JCJtXNZrOsZ2mMRiPa29sBnH/PkpKSmJRZONwRuLK4uIjW1lZUVFQgNzdXkGva7XZ84hOfgNFoxCuvvCKpeeq9996L++67z+NrFRUV6O3tleiO5IciLF584xvfwOrqKh5++GGmoE9RFM6dO4ejR4/imWeewfT0NK688ko0NTXhmmuu4dx9RZyY+e6xn52dRVdXl6AzKkITqkULRVEwGo2MGAMQtXZFhgodDofkk+qBsNlsOHfuHFJSUrBt2zZmuJftjkDSr1JEfAQiKkK6UjscDtx8882YmprCyZMnJfeQu/fee3H06FGcPHmS+ZpWqxXMlflCQBEWL9xud8DVx8Qy49ixYzh+/DiGh4dxxRVX4NChQzh48CDnaXSy2XB+fj4ok8yxsTEMDQ0JurNCaISyaKFpGouLi8wB6nK5PCI+vmlF0qoLQNbzH1arFc3NzUhNTfXpKuF0Oj2K/6RFl0R84UqXLS0toaWlRVBRcTqd+MxnPoPBwUG8/vrrsvjM33vvvThx4gQzKK2wFkVYeEB2XRw9ehTHjx9HT08PLrvsMjQ1NeHgwYOcVyh7G0D6Mskka2VnZ2fDtpUyFMSyaGG76M7NzcFms/Gy/Hc4HGhtbZV9qy6x58/IyEBFRcW6nyd2xKfX60HTtEfxX6zvc2lpCa2trSgrKxNMVFwuFz7/+c+jvb0dp06dCssKBS7ce++9eOihh5CUlITo6Gg0NjbiwQcfREFBgdS3JhsUYREImqYxMDDAiExbWxs+8IEP4NChQ7jhhhuQmZnJ2YmZHJ5LS0tITExERkYGlpaWYLFYUFtbK6t8OhsyAU5RlOgWLXws/+12O5qbmxEXFxe2rqqBARWGh1XYupVGaSm3Xzmz2Yzm5mZkZWWFNPNDBldJxMcW4/T0dMHSfsvLy2hpaUFpaalgbb9utxtf+tKX8M477+D06dOySvn+/e9/h8lkQkVFBWZmZnDfffdhamoKnZ2dYWlP3wgowiICNE1jZGSESZedO3cOjY2NzE6ZnJwcziaZMzMzGB4ehsvlQnx8PLZs2SKb9lw2Ulq0BGP5T9JK4Zr6NxqBz3wmCidPvv9+XHGFG088YUcgQ2yTyYTm5mbk5ORwdu4OBE3TMJvNjMisrq4iOTmZifhC7YIjorJ161bBntgpisJ//ud/4vTp0zh16pTsI4GlpSUUFhbiZz/7GT73uc9JfTuyQBEWkaFpGhMTEzh+/DieeeYZnDlzBvX19YxJZkFBgd9Dw2azobW1FdHR0aiqqoLRaGScmGNiYph0mRhW9sFADmvioSZlC2wgy3+VSsWsEOCSVhKCQ4eicOqUGm73+6+l0dC47DIKzz5r9/lvVldX0dzcjPz8fJSUlIhynyQyJusjQtlnL5ao3Hnnnfj73/+OU6dOobi4WJDris3u3btxxRVX4MEHH5T6VmSBIixhhKZpzMzMMDtl3nzzTezatYvZKbN161bmF3pqagpDQ0M+Z1SISebc3BwWFhaYXSCZmZmc7UCEgux7kaNFi7flP0VRSExMREVFRVh2pgwMqFBT4z8SaGuzrkmLraysoKWlBQUFBSgpKRH1/gjs98lgMCAyMtKj+O/rfVpZWUFzczNKSkpQWFgoyH1QFIVvfetbOH78OE6fPo3S0lJBris2JpMJBQUFuPfee/HVr35V6tuRBYqwSARN01hYWMCJEydw7NgxvP7666isrERTUxNSU1Pxve99D0899RSuvPLKgAcg2QXiPQOSlZUl+uFJFl/JfZaGdCtlZGRApVIxlv/k8AzF8p8LL7+sxo03+m9eOH7chquvppj/JhFAcXExioqKBL8fLhB/LvJ5AuCxW0aj0YgmKvfeey/+9Kc/4fTp06ioqBDkumLw9a9/Hddffz0KCwsxPT2Ne+65BzqdDt3d3cjIyJD69mSBIiwygLTVPvfcc3jkkUeYdM1nP/tZNDU1cU4vURTFtJ3Oz88znlPk8BTy4Nfr9ejo6BC0tVQMyAR4WVkZU1j29nmjKEoUC55gIhbSVSXkYc0XYlVERMZutyM5ORnLy8soLCzE1q1bBXudH/7wh/jNb36DU6dOYfv27YJcVyw+/vGP480334TBYEBGRgYuvvhiPPDAA4K9HxcCirDIiF/84hf4zne+g9/+9rdwOp04duwYXn75ZeTk5DA7ZWpqajiLzOLiIubm5pi2UxLJ8H1C92XRIkeI+AWyFfFl+c9uY+Y728KlxkKGCtniJzdomsb8/Dw6OzsREREBh8OBlJQURpBDbSunaRo//elP8fDDD+P1119HdXW1wHeuIAWKsMiEhYUFXHLJJXjiiSc8PI9MJhNefPFFHDt2DH//+9+RlpbmsVOGi0CQiIgcnsQkMysri0lvcIUMaIq5REwIiDtBMOLny/KfLObKzMwMqT13cRH49Kf9d4WRiErukR9pKCBpT6vVyhT/l5aWkJCQwIhMXFwcp+iYpmk8/PDDeOihh/DKK6+goaEhDN+JQjhQhEVGUBQVUCgsFguzU+Zvf/sb4uLiPHbKcBEI9hP63NwcnE4ncyAEmmZnW7TU1tbKdkATACYnJ9Hf38/bnYBt+b+yssKrPXdwUIWhIc85FrKnREijRjEwmUw4d+4cIyreeBuKRkVFMWLsr85H0zR+/etf4/7778dLL72Effv2heNbUQgTirBsUGw2G06ePMnslImIiMD111+PpqYmj50ygSAmmcS/zGazMVPa7DQQ2UtjMBh4WbSEA7KWuaamBimBBkWCxLs9l6/lP0nT8d1TIjZEVLh2qZGNq2Q5l0ql8lhZrVarQdM0fvvb3+K73/0uXnjhBVx88cVh+E4UwokiLBcATqcTp0+fZnbKuN1uXHfddWhqasKll17KKYVDBui8TTIzMjIYy3Y5O//SNI3h4WFMTEyIHlHxtfyfn59HR0eH7GtUZEgzLy8vpMI02fhIBPm3v/0tDAYDsrKy8Nxzz+Fvf/sbLr30UuFvXEFyFGG5wCA7Zf7617/ixIkTsFgsuPbaa3Ho0CFcccUVnIXBbDZjdnYWY2NjcLvdSElJYab+5eYAzPZRq6+vR3x8fNheO1jL/7m5OXR2dmLnzp3IzMwM230Gi9lsxrlz50IWFW9omsY///lP3H///Th16hQ0Gg0uv/xyNDU14YYbbhDMXl9BHijCcgHjdrtx9uxZJpJZXFz02CkTyHPM4XCgpaUFkZGRKC8vZ57Q2bUGPt1AQkHTNHp6emAwGFBfXy+pj9p6lv9zc3Po7u7Grl27ZD3vQEQlNzfXY2iXL0ePHsUXv/hFPP3006isrMSJEydw4sQJlJeX43//938FeQ0FeaAIyyaBoij885//ZHbKzM7OeuyUYZvnLS8vo6Ojw6dFi81mYw5OYpJJrGXCvXWRoih0dnZidXUV9fX1koscG/YMCGljpmkaRUVFKCoqkq1Fv1ii8txzz+Fzn/sc/r//7//DoUOHPP5svaYVsXj00Ufx0EMPYXZ2FtXV1XjkkUewZ8+esN/HhYgiLJsQiqKg0+kYk8zR0VFmp8yWLVtwyy234Fe/+hWuuuqqgAeLw+FgusuI3xQRGbEL/G63G+3t7bDb7bJe0AUAExMT6O/vR2ZmJlZXV2G1WnlZ/osFcVPOzs4WxPiS8OKLL+LIkSN48skn8dGPflSQa/LlL3/5Cw4fPoxf//rX2Lt3L37+85/jr3/9K/r6+mSdotwoKMKyyaFpGl1dXTh69Cj+8Ic/YHh4GIWFhbjzzjtxww03IDU1ldMB43Q6Gbt/g8HAmD9mZWVxnmvgisvlQltbG9xut+j2/HyZmJjAwMAAamtrmS41k8nEvFdsy/+MjAzJoi6LxYJz585hy5YtKCsrE+zndfLkSXzyk5/Eb37zG3ziE58Q5JpCsHfvXuzevRu//OUvAZx/2MrPz8dXvvIV3H333RLf3cZHERYFAMALL7yAj33sY7jzzjsRERGBY8eOob29HR/4wAfQ1NSE66+/nvNOGZfLxXQCEZNMEsnwXZvrdDrR2toKjUaD6upq2aaUgPdbn2tra/3uaCeDhnNzc+ta/ouFWKLyxhtv4KabbsKjjz6Kw4cPy8ZLzuFwIDY2FkePHkVTUxPz9SNHjmBpaQnPPvusdDd3gaAIiwIWFxdRUVGBRx99FDfddBOA99t3SbqsubkZjY2NzOIyrjtliEkmcWKOjIxcd3jOHw6HA83NzYiJicHOnTtlu/URAEZHRzEyMoK6ujrOrc+BLP/FWo1ANlRmZmYK6k79j3/8Ax/5yEfws5/9DP/xH/8hG1EBzlsS5ebm4syZM2hsbGS+ftddd+GNN97Au+++K+HdXRgowqIAAMxKZF+QnTLHjh3DM888g7Nnz6KhoQGHDh3CoUOHAu6UYUOcc4l/WTAmmTabDc3NzUhMTJR858t6DA8PY3x8HHV1dUhMTAzpGt6W/1ym2YPFarXi3LlzgovKu+++i6amJvzwhz/EF7/4RVmJCqAISzjYNMLywAMP4IUXXoBOp0NkZCSWlpbW/J3x8XHceuutOHXqFOLj43HkyBE8+OCDsk63hBuapjE9Pc3slHnrrbdQXV3N7JThupjKuzWXTGj7MskkT9VpaWmoqqqS3UFFoGkaQ0NDmJycRH19vWBrask0O5mVUavVHoIcisgSUcnIyBB06VlzczNuuOEG3HPPPfjP//xPWf6slFSY+GwaYbnnnnuQnJyMyclJ/Pa3v10jLG63GzU1NdiyZQseeughzMzM4PDhw7jlllvwwx/+UJqbljk0TUOv1zM7ZU6dOoWqqipGZLgeWGRCm3SY0TTN+JdFRUWhtbUV2dnZgub/hYZ4qU1PT4s6pCmE5b9YotLW1oaDBw/i7rvvxp133inbnxVwvni/Z88ePPLIIwDOv68FBQX48pe/rBTvBWDTCAvhiSeewNe+9rU1wvL3v/8d1113HaanpxmbjV//+tf4xje+Ab1eL+t2VjlAHJSfffZZHDt2DCdPnsTWrVsZu3+u++WJSebc3BxmZ2fhcDgQHx+PkpKSgCaZUkIm/+fm5lBfXx82L7VAlv/p6ek+u+VsNhvOnTuHtLQ0VFZWCnb4d3V14cCBA/jP//xPfOc735G1qADn242PHDmC//mf/8GePXvw85//HE8//TR6e3tlbbOzUVCE5V9873vfw3PPPQedTsd8bWRkBCUlJWhpaUFtbW14b3SDs7y8jOeff57ZKZObm4umpiY0NTWhurp6XZEhO0pyc3OhVqsxNzcHu92O9PR0ZGVlIT09XRYpSpqm0dfXB71eL+nkPxfLfyIqqampgqYUe3t7ceDAAXz+85/H97//fdmLCuGXv/wlMyBZU1ODhx9+GHv37pX6ti4IFGH5F5///OcxNjaGl19+mfmaxWJBXFwcXnzxRRw4cCDMd3rhsLq66rFTJj09nYlkGhoa1ojMwsIC2tvbPXaUsA/Oubk5WQwZsu1kGhoawu48EAhvy/+EhARYrVakpqZi586dgh3+AwMDOHDgAD71qU/hRz/6kaybKhTCx4b+FNx9991QqVQB/9fb2yv1bW56EhIS8LGPfQxPP/005ubm8F//9V8wGAxoampCVVUV7rzzTrz99ttwu934/e9/j/vvvx/btm3zWHylUqmQkJCArVu3Yv/+/di3bx8SExMxPj6ON954Ay0tLZicnITD4QjL90TTNLq7u2E0GrF7925ZiQoAxMbGoqioCHv27MHu3bthtVqhUqkwPz+Pd999FyMjIzCZTLxeY2RkBNdddx1uuukmRVQUPJA+l8CDO+64A5/+9KcD/h0uOyQAYMuWLXjvvfc8vjY3N8f8mYIwxMbG4sYbb8SNN94Im82GV199FcePH8fHP/5xuFwumM1m3Hbbbetup4yLi0NJSQlKSkqYp/Pp6Wn09vYiOTkZWVlZok2yUxSFrq4urKysoKGhQVYeZd7Y7XZ0dnYiIyMD27Zt8xheHR4eDtrynzA+Po5rr70W1113Hf77v/9bERUFD5RU2L8gxfuZmRnGK+jxxx/HnXfeifn5eURFRUlwt5uHX/ziF/jWt76FSy65BM3NzaAoCgcPHsSHP/xhXHLJJZybJ4hJJplkT0pKYg5OIaIKYnxpMplQX18v68+F3W7HuXPnGDNRb9FgW/4vLCxAq9UGtPwnTE9P46qrrsLll1+Oxx9/XBEVhTVsGmEZHx+H0WjEc889h4ceeghvvfUWAKC0tBTx8fFMu3FOTg5+8pOfYHZ2FjfffDP+4z/+Q2k3FpnHHnsM3/72t/Hiiy9i3759cLlceOutt/DXv/4Vzz77LCwWCw4ePIimpiZcfvnlnCMEu93O1BnYWx+zsrJCKrJTFIWOjg5YLBbU19fLulPQbrd7DJSuF4msZ/lPxGN2dhYHDhzA3r178fvf/16WXXrhhKZpXHnlldBoNB71WQD41a9+hW9961vo7Oz0SOtuBjaNsHz605/Gk08+uebrp06dYrbYjY2N4dZbb8Xp06cRFxeHI0eO4Ec/+pEsuo8uZEZGRrC6uopdu3at+TO3240zZ84wO2WWlpZwzTXXoKmpCVdeeSVngXA4HEwKiJhkEv8yLjMnFEWhra1tQ7gpOxwOnDt3DgkJCdixY0fQhXpvy//e3l68+OKLuPTSS/HEE0+grq4Of/jDH5Tfi38xMTGBnTt34sc//jG+8IUvADj/md65cycee+wx3HzzzRLfYfjZNMIiJ4qKijA2NubxtQcffFAZzFoHiqLw3nvvMSIzOzuLq666Ck1NTbj66qs5T7oTuxTixEzqDFlZWT49udxuN9ra2uB0OlFXVydrN2W+ouINaVJ45JFH8Oc//xlutxvXXnstPvrRj+L6669HamqqQHe+sXnyySfx5S9/Ge3t7SgqKsKHPvQhJCcn4/jx41LfmiQowiIBRUVF+NznPodbbrmF+VpCQkLYBusuBCiKQmtrK2OSOTY2hiuuuAJNTU249tprkZiYyNmJ2WAwrDHJzMrKQmJiIrO7xu12o66uTtZP6cSkMy4uDjt27BCs9rG4uIjrr78eeXl5uP/++/HCCy/gmWeeQXd3N/R6vaRbO+VEU1MTlpeXceONN+L+++9HV1eXrDeFiokiLBJQVFSEr33ta/ja174m9a1cENA0jc7OThw9ehTHjx9Hf38/Lr/8chw6dAgHDx7kvFPG25NLo9GApmlERkaioaFB9pGKGKKysrKCG264AWlpaXjmmWc86lt6vV4WB6dcMgDz8/PYvn07jEYjjh075uFDttlQhEUCioqKYLPZ4HQ6UVBQgE9+8pO47bbbZP00vFEgk/DHjh3DsWPH0NnZ6bFTJiMjg5PIOBwO/POf/4Tb7QZFUVCpVLyNH8XC4XCgpaWFWScg1L2ZTCY0NTUhNjYWzz//vOxmdQhyygB85zvfwYkTJ9DZ2Rn215YTykkmAV/96ldRV1eH1NRUnDlzBt/85jcxMzODn/3sZ1Lf2oZHpVKhsrIS3/72t/Gtb30LQ0NDOHbsGP74xz/i9ttvR2NjI5qamnDDDTcgOzvbp8iQZWIxMTGorq6GSqVijB87OzsZk8ysrCyPjikpcDqdooiK2WzGRz/6UURERODEiROyFRVCQkKCLObNtFqt8oAIJWIRjLvvvhs//vGPA/6dnp4eVFZWrvn67373O3zhC1+AyWSS9VzERoamaYyPj3vslNmzZw+zUyY/Px8qlQpzc3N45513UFBQ4NPTzLtjyuVyBe0uLBROp9Nj8ZlQomK1WvFv//ZvsNlseOmllwSz/xcLOWUA7r33Xpw4ccLDc3AzogiLQOj1ehgMhoB/p6SkxGebaldXF3bs2IHe3l5UVFSIdYsK/4LslDl+/DiOHz+Of/zjH6ipqcHll1+OP//5z9izZw9+//vfr3tQ0zSNlZUVZiDT4XAgPT2dcRcW82AjohIdHY1du3YJJip2ux2f+MQnYDQa8corr/hdqSwnfvazn63JAHzmM5+RJAOgCMt5FGGRAX/6059w+PBhLCwsICUlRerb2VTQNI35+Xk8+eST+P73vw+LxYKdO3cyTsxcNysSk8y5uTnMz88zJpnEiVnIwj9Jf0VGRnJyiuaKw+HAzTffjKmpKZw8eVLSVuKNmgFQhOU8irCEmbNnz+Ldd9/FZZddhoSEBJw9exa33XYbDhw44HOAU0F8pqencfnll6OhoQH/9V//hRdeeAHHjh3Da6+9htLSUsaJuaqqivMhznZiJhb2xL+Mz3Alqf9EREQIKipOpxOf/vSnMTQ0hNdffx3p6emCXDdUlAzAxkYRljDT0tKCL37xi+jt7YXdbkdxcTFuvvlm3H777Up9RSL+93//F2+//Tb+93//l6mRkCVaZKfMK6+8gry8PBw6dAgf/vCHg0o/EZPMubk5rK6uIiUlhekwC+Zn7nK50NLSIriouFwu3HLLLejo6MCpU6c2/KIrJQMgPYqwKCjgvJAESnmtrq4ykcxLL72E9PR0HDp0CE1NTT53yvjDarUyhX9ikkmsZQJ5oBFR0Wq1qK6uFqxJwO1249Zbb8V7772HN954A9nZ2YJcN1woGQB5ogjLJuDRRx9lNuVVV1fjkUcewZ49e6S+rQ2L2WzGSy+9hOPHj+OFF15AYmIibrjhBhw6dAj79u3jfOj7MskkIsOeZne5XGhtbYVGoxFUVCiKwle+8hW89dZbOHXqFPLz8wW5bjhRMgDyRBGWC5y//OUvOHz4MH79619j7969+PnPf46//vWv6OvrY9YDKISO1Wpldso899xziIqKwg033ICmpiZcdNFFnDvDHA4HIzJGoxHx8fFMC3N/fz/UajVqamoEFZU77rgDL7/8Mk6fPo2ioiJBrqugACjCcsGzd+9e7N69G7/85S8BnD9Q8vPz8ZWvfEUxvRQYh8OBU6dO4ejRo3j22WcBgNkp88EPfpBz0d7pdEKv1zP+ZRqNBvn5+diyZYtPk8xgoSgK3/zmN3HixAmcOnUKpaWlvK6noOCNIiwXMA6HA7GxsTh69KiHb9GRI0ewtLTEHH4KwuNyufDmm28yO2VsNhuzU+ayyy5bd6eM2+1Ga2sraJpGbm4uFhYWoNfrERUVxaTLuBptsqEoCvfccw/+7//+D6dOnVK6phREQRGWC5jp6Wnk5ubizJkzaGxsZL5+11134Y033sC7774r4d1tHtxuN95++23G7n95eRkHDhxAU1MTrrjiijXuwERUAKC2tpZJfxGTTBLJkI2PWVlZSEpKWldkaJrGAw88gN/+9rd4/fXXsX37dnG+YYVNj3yc9BQULlA0Gg0++MEP4uGHH8bo6Cheeukl5Obm4tvf/jaKiopw88034+jRozCZTDCZTDhy5AgWFxc9RIVcJzMzEzt37sQll1yCyspKuFwu6HQ6vPnmm+jp6YHRaARFUWvugaZpPPTQQ3j88cfx6quvKqKiICpKxHIBo6TC5A1FUWhpaWF2yoyPjyMmJgbx8fF45ZVXGP8yLtchJpnz8/OgaRqZmZmIiYlBdnY2oqKi8PDDD+Ohhx7Cq6++ivr6+jB8dwqbGUVYLnD27t2LPXv24JFHHgFw/hAqKCjAl7/8ZaV4LyOsViuuuOIKjI+PIy4uDiMjI8xOmeuuuw4pKSmcrWWWlpYwNzeHX/ziFzh27BiKioowMjKCv//97/jgBz8Yhu9GYbOjpMIucG6//Xb85je/wZNPPomenh7ceuutMJvN+MxnPiP1rSn8C4fDgRtvvBE0TaOrqws9PT3Q6XTYv38/Hn/8cZSUlODQoUP43e9+B71ej0DPgiqVCikpKaisrMQjjzyCI0eOoL+/H0lJSTh48CA+/vGP4+jRowGvoaDAFyVi2QT88pe/ZAYka2pq8PDDD2Pv3r1S35bCv6BpGo8++igOHz6MxMTENX82NDSEo0eP4plnnkFLSwv279/P7JTZsmWLz0iGpmk89dRTuOuuu/D888/jkksugU6nw7FjxzAwMIC//OUv4fr2FDYjtIKCCNxzzz00AI//VVRUSH1bGxqKouiRkRH6pz/9KX3RRRfRGo2G3r9/P/3jH/+Y7u3tpU0mE202m2mTyUT/5je/oePi4uhXX31V6tv24Ac/+AHd2NhIx8TE0ElJST7/ztjYGH3ttdfSMTExdEZGBv31r3+ddjqd4b1RBV4oq84URGP79u04efIk89/KZj1+qFQqFBUV4Y477sDtt9+OqakpZqfMt7/9bdTW1uLQoUOIjo7GPffcg6effhpXXHGF1LftgcPhwE033YTGxkb89re/XfPnbrcbBw8exJYtW3DmzBnMzMzg8OHDiIiIwA9/+EMJ7lghJKRWNoULk3vuuYeurq6W+jY2BRRF0TMzM/Rjjz1Gf/CDH6QB0E8++aTUtxWQ3//+9z4jlhdffJFWq9X07Ows87XHHnuMTkxMpO12exjvUIEPSvFeQTQGBgaQk5ODkpIS/Pu//zvGx8elvqULEpVKhS1btuD//b//h9OnT2N0dBSHDx+W+rZC4uzZs9i5c6eHdf/VV1+NlZUVdHV1SXhnCsGgCIuCKOzduxdPPPEEXnrpJTz22GMYGRnBBz7wAayurkp9axc0KpUKhYWFUt9GyMzOzq7ZB0P+e3Z2VopbUggBRVgUROHAgQO46aabsGvXLlx99dV48cUXsbS0hKefflrqW1MQmLvvvhsqlSrg/3p7e6W+TYUwolRTFcJCcnIy/v/27iekyT+OA/h7DNFm1LZcrEGROqQEG140ZYfUcNuhMLvYYTmKAhPDSKS/G6ULyygKTAn8c4m6OapLWTQ9KHrpiQ4d2tDGiBYUKx6jIHs6/Nh++dOfeXjc183367Q9z8P2vr19Hvf9fIuKihAKhURHIZWdPn0aHo9nyWsKCgqW9VlmsxlTU1PzjsViseQ5Sg8sFkoJWZYRDofhdrtFRyGVmUwmmEwmVT6roqICfr8fHz9+TO4XNDIygg0bNqC4uFiV76CVx0dhtCLa2towOjqKmZkZjI+P48CBA9BqtTh06JDoaCRQJBKBJEmIRCKYm5uDJEmQJAmyLAMAamtrUVxcDLfbjVevXuHJkye4cOECmpubuSNkGuHKe1oRDQ0NGBsbw6dPn2AymWC32+H3+1FYWCg6Ggnk8XgW3Yv+xYsX2LNnDwDg3bt3aGpqQjAYRG5uLhobG9HV1cV1UGmExUJERKrio7A0Nzc3h8rKStTX1887/uXLF2zduhXnz58XlCz1xsbGsG/fPlgsFmg0GgQCgXnnFUWB1+vFli1bsG7dOuzduxdv374VE5Yog7FY0pxWq02uF7l3717yeEtLC4xGI3w+n8B0qTU7OwubzYaenp5Fz1+7dg23b99GX18fJicnkZubC4fDge/fv6c4KVGGE7run1Rz69YtxWAwKO/fv1cCgYCSlZWlSJIkOpYwAJTh4eHk+1+/films1np7u5OHovH40p2drZy//59AQmJMhfvWDJES0sLbDYb3G43jh8/Dq/XC5vNJjrWqjE9PY0PHz7MG8q4ceNGlJeXY2JiQmAyoszDn1lkCI1Gg97eXuzcuRMlJSXcHfI/EuNAFhsXwlEhROriHUsGGRgYgE6nw/T0NKLRqOg4RLRGsVgyxPj4OG7evInHjx+jrKwMR48e5fazf0iMA0mMB0mIxWIcFUKkMhZLBvj27Rs8Hg+amppQVVWF/v5+TE1Noa+vT3S0VSM/Px9msxnPnz9PHvv69SsmJydRUVEhMBlR5mGxZICzZ89CURR0dXUBALZv347r16+jvb0dMzMzYsOlkCzLyREhwD//sE+MD9FoNGhtbUVnZycePnyI169f4/Dhw7BYLKirqxOaOx34/X5UVlZCp9NBr9cves1iU40fPHiQ2qC0KnDlfZobHR1FTU0NgsEg7Hb7vHMOhwM/f/7Es2fPoNFoBCVMnWAwiKqqqgXHGxsbMTQ0BEVR4PP5cPfuXcTjcdjtdty5cwdFRUUC0qYXn88HvV6PaDSK/v5+xOPxBddoNBoMDg7C6XQmj+n1euTk5KQwKa0GLBYiWrahoSG0trb+b7EMDw/zDpD4KIxILX8bKePxeBY8Kvrzr/tM0NzcjLy8PJSVlWFgYIA/IFmjuI6FSCWJkTJHjhxZMLstwel0YnBwMPk+k0bBX758GdXV1dDpdHj69ClOnDgBWZZx8uRJ0dEoxVgsRCpxuVxwuVxLXpOdnb1qft585swZXL16dclr3rx5gx07dizr8y5evJh8XVpaitnZWXR3d7NY1iAWC1EKBYNBbN68GQaDAdXV1ejs7MSmTZuEZFFzS+HFlJeXo6OjAz9+/MioOzP6OxYLUYo4nU7U19cjPz8f4XAY586dg8vlwsTEBLRabcrzqLml8GIkSYLBYGCprEEsFqIUaWhoSL4uKSnBrl27UFhYiGAwiJqaGoHJ/i4SieDz58/zthQGAKvVivXr1+PRo0eIxWLYvXs3cnJyMDIygitXrqCtrU1scBKCxUIkSEFBAfLy8hAKhVZ9sXi93nlbCpeWlgL4d0vhrKws9PT04NSpU1AUBVarFTdu3MCxY8dERSaBuI6FaAUsZ01HNBrFtm3bEAgEsH///tSFI1phvGMhUoksywiFQsn3iZEyRqMRRqMRly5dwsGDB2E2mxEOh9He3g6r1QqHwyEwNZH6eMdCpJKlRsr09vairq4OL1++RDweh8ViQW1tLTo6OhbsEUOU7lgsRESkKo50ISIiVbFYiIhIVSwWIiJSFYuFiIhUxWIhIiJVsViIiEhVLBYiIlIVi4WIiFTFYiEiIlWxWIiISFUsFiIiUtVv5IWItTFC6WgAAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 可视化下三维情况下的向量空间\n",
    "\n",
    "fig = plt.figure()\n",
    "ax = fig.add_subplot(111, projection='3d')\n",
    "\n",
    "colors = ['r', 'g', 'b', 'y']\n",
    "for i in range(len(data_transformed)):\n",
    "    ax.scatter(data_transformed[i, 0], data_transformed[i, 1],\n",
    "    data_transformed[i, 2], c=colors[labels[i]])\n",
    "\n",
    "ax.set_xlabel('X')\n",
    "ax.set_ylabel('Y')\n",
    "ax.set_zlabel('Z')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5adf5105-34d7-49d5-9c88-eca64e6a5e88",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "27c3304a-0ed7-43a6-be8c-11a32d08f913",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "07531220-97c4-442a-a9a1-8e53640a623f",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9a172468-945d-4a70-8a68-b67fe0afb049",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "00a0d0e4-5a9e-4324-903b-f72b94f8c7d2",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "### 第二架马车：Vector Database"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "ce973dd6-1d6f-46a6-a3ca-3d5ed8b52278",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 用传统的数据库来做存储功能也可以，但是基于向量查询就需要在程序内存中处理\n",
    "# 支持私有化部署，对于扩展性、可用性、性能都有高要求 （Milvus）\n",
    "# https://milvus.io/docs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "bf897fd7-ac3d-44a9-9ea3-969dfdf7916e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 哟哟切克闹，增删查改来一套\n",
    "\n",
    "from pymilvus import connections, utility, Collection, db, CollectionSchema, FieldSchema, DataType\n",
    "\n",
    "connections = connections.connect(host=\"127.0.0.1\", port=19530, user=\"root\", password=\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ea4c24c0-6f4c-4efc-9916-a74f56d046cc",
   "metadata": {},
   "source": [
    "#### Database"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "c47089ee-d038-4202-9868-b7c87429ad2a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 默认系统会自动创建一个名为 default 的数据库\n",
    "database_name = \"mowenxidong\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "1296a7b6-7745-44f6-8829-f4042a40e20d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建一个数据库\n",
    "db.create_database(database_name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "ff47745b-0d8f-49d7-b0de-a6593e28ea56",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['default', 'mowenxidong']"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查看实例下有多少个数据库\n",
    "db.list_database()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "39df1a63-2eee-4b3b-857f-15e56f655401",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 删库跑路\n",
    "db.drop_database(database_name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e9e60a80-fdef-4a39-a3dd-683bb2fbffb4",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "d03f6371-0192-4673-a7cc-fe7658014dcc",
   "metadata": {},
   "source": [
    "#### Collection"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "54660833-35d9-41b8-b76d-59a0ce2ec237",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Collection 类似传统数据库中的 Table 概念\n",
    "database_name = \"default\"\n",
    "collection_name = \"mobot\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "cc008fb2-27a8-4807-b8bc-6b4f08994c9c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建集合\n",
    "\n",
    "# 1. 声明 Field 类型及属性\n",
    "fields = [\n",
    "    FieldSchema(name=\"id\", dtype=DataType.INT64, is_primary=True),\n",
    "    FieldSchema(name=\"question\", dtype=DataType.VARCHAR, max_length=1000),\n",
    "    FieldSchema(name=\"answer\", dtype=DataType.VARCHAR, max_length=1000),\n",
    "    FieldSchema(name=\"embedding\", dtype=DataType.FLOAT_VECTOR, dim=1024)\n",
    "]\n",
    "\n",
    "# 2. 根据字段声明 Schema\n",
    "schema = CollectionSchema(\n",
    "    fields=fields,\n",
    "    description=\"\",\n",
    "    enable_dynamic_field=True\n",
    ")\n",
    "\n",
    "# 3. 根据 Schema 声明 Collection\n",
    "collection = Collection(\n",
    "    name=collection_name,\n",
    "    schema=schema,\n",
    "    using=database_name,\n",
    ")\n",
    "\n",
    "# 4. 创建 Field 的索引\n",
    "index_params = {\n",
    "    'index_type': \"IVF_FLAT\",\n",
    "    'metric_type': \"L2\",\n",
    "    'params': {'nlist': 1}\n",
    "}\n",
    "collection.create_index(field_name=\"embedding\", index_params=index_params)\n",
    "\n",
    "# 5. 挂载 Collection，变成可用状态\n",
    "collection.load()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "0910244d-1ffb-4f8e-9be3-301487266ee8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Collection>:\n",
       "-------------\n",
       "<name>: mobot\n",
       "<description>: \n",
       "<schema>: {'auto_id': False, 'description': '', 'fields': [{'name': 'id', 'description': '', 'type': <DataType.INT64: 5>, 'is_primary': True, 'auto_id': False}, {'name': 'question', 'description': '', 'type': <DataType.VARCHAR: 21>, 'params': {'max_length': 1000}}, {'name': 'answer', 'description': '', 'type': <DataType.VARCHAR: 21>, 'params': {'max_length': 1000}}, {'name': 'embedding', 'description': '', 'type': <DataType.FLOAT_VECTOR: 101>, 'params': {'dim': 1024}}], 'enable_dynamic_field': True}"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查看 Collection 详情\n",
    "Collection(collection_name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "55002e9c-34f5-4f0e-802f-e4a930ee8a46",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 删库大法\n",
    "utility.drop_collection(collection_name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "f3f96346-b9ab-43d3-b2ea-7361b1a2c9fc",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 判断 Collection 是否存在\n",
    "utility.has_collection(collection_name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ddac5b89-47d3-4c04-8b16-f594a8883186",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "d45bd3d9-8f01-40b6-b0fe-7acaf17d8ee3",
   "metadata": {},
   "source": [
    "#### Document"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "1182cfbd-780d-4bc6-823c-53d15bd1342b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 新增数据\n",
    "knowledge_base = [\n",
    "    {\"question\": \"小盖是谁\", \"answer\": \"小盖是墨问西东最帅的男人。\"},\n",
    "    {\"question\": \"瑶瑶是谁\", \"answer\": \"瑶瑶是墨问西东的颜值担当，遥遥领先。\"},\n",
    "    {\"question\": \"皮肤很干\", \"answer\": \"这个季节就是皮肤的水分流失相当快，要特别注重补水哦！我们新出了一款产品，使用后皮肤水汪汪的，滋润而不油腻。是我们卖的最好的明星产品之一。\"}\n",
    "\n",
    "]\n",
    "\n",
    "ids, questions, answers, vectors = [], [], [], []\n",
    "\n",
    "for i in range(len(knowledge_base)):\n",
    "    ids.append(i+1)\n",
    "    questions.append(knowledge_base[i]['question'])\n",
    "    answers.append(knowledge_base[i]['answer'])\n",
    "    vectors.append(embed_model.encode(f'{knowledge_base[i][\"question\"]}\\n\\n{knowledge_base[i][\"answer\"]}'))\n",
    "\n",
    "\n",
    "entities = [ids, questions, answers, vectors]\n",
    "\n",
    "collection.insert(entities)\n",
    "collection.flush()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "7b7c96ed-9de2-43aa-99f3-19147553fd9c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 基于向量的相似度搜索\n",
    "\n",
    "data = embed_model.encode([\"爆嗮之后掉皮\"])\n",
    "top_k = 3\n",
    "\n",
    "documents = collection.search(\n",
    "    data=data,\n",
    "    anns_field=\"embedding\",\n",
    "    param={\"metric_type\": \"L2\", \"params\": {\"nprobe\": 1}, \"offset\": 0},\n",
    "    limit=top_k,\n",
    "    output_fields=[\"*\"]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "7dc2c334-d5e8-4f73-9141-2e901ff9fcd1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3\n",
      "856.0146484375\n",
      "皮肤很干\n",
      "这个季节就是皮肤的水分流失相当快，要特别注重补水哦！我们新出了一款产品，使用后皮肤水汪汪的，滋润而不油腻。是我们卖的最好的明星产品之一。\n",
      "----------------\n",
      "1\n",
      "1062.521484375\n",
      "小盖是谁\n",
      "小盖是墨问西东最帅的男人。\n",
      "----------------\n",
      "2\n",
      "1079.0732421875\n",
      "瑶瑶是谁\n",
      "瑶瑶是墨问西东的颜值担当，遥遥领先。\n",
      "----------------\n"
     ]
    }
   ],
   "source": [
    "for i in range(len(documents[0])):\n",
    "    print(documents[0][i].id)\n",
    "    print(documents[0][i].distance)\n",
    "    print(documents[0][i].entity.get('question'))\n",
    "    print(documents[0][i].entity.get('answer'))\n",
    "    print('----------------')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "f61cbf8b-95c1-4302-ab54-8229a503527c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 基于标量的查询\n",
    "expr = 'id != 3'\n",
    "\n",
    "documents = collection.query(\n",
    "    expr=expr,\n",
    "    offset=0,\n",
    "    limit=top_k,\n",
    "    output_fields=[\"*\"]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "c3559f8d-94e3-4ad4-aad4-ac2fa6fc181b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n",
      "小盖是谁\n",
      "小盖是墨问西东最帅的男人。\n",
      "----------------\n",
      "2\n",
      "瑶瑶是谁\n",
      "瑶瑶是墨问西东的颜值担当，遥遥领先。\n",
      "----------------\n"
     ]
    }
   ],
   "source": [
    "for i in range(len(documents)):\n",
    "    print(documents[i]['id'])\n",
    "    print(documents[i]['question'])\n",
    "    print(documents[i]['answer'])\n",
    "    print('----------------')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "ba9974c7-6b86-4ada-bd4d-39d7d2986823",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 混合检索\n",
    "data = embed_model.encode([\"爆嗮之后掉皮\"])\n",
    "expr = 'id != 3'\n",
    "\n",
    "documents = collection.search(\n",
    "    data=data,\n",
    "    anns_field=\"embedding\",\n",
    "    param={\"metric_type\": \"L2\", \"params\": {\"nprobe\": 1}, \"offset\": 0},\n",
    "    limit=top_k,\n",
    "    output_fields=[\"*\"],\n",
    "    expr=expr\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "4421ef4a-549b-45d1-ac75-5cab6bcd1c38",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n",
      "1062.521484375\n",
      "小盖是谁\n",
      "小盖是墨问西东最帅的男人。\n",
      "----------------\n",
      "2\n",
      "1079.0732421875\n",
      "瑶瑶是谁\n",
      "瑶瑶是墨问西东的颜值担当，遥遥领先。\n",
      "----------------\n"
     ]
    }
   ],
   "source": [
    "for i in range(len(documents[0])):\n",
    "    print(documents[0][i].id)\n",
    "    print(documents[0][i].distance)\n",
    "    print(documents[0][i].entity.get('question'))\n",
    "    print(documents[0][i].entity.get('answer'))\n",
    "    print('----------------')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "94cd2729-12e3-445f-b1f5-70a43a99494e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n"
     ]
    }
   ],
   "source": [
    "# 删除前先查下是否存在\n",
    "expr = 'id in [3]'\n",
    "\n",
    "documents = collection.query(\n",
    "    expr=expr,\n",
    "    offset=0,\n",
    "    limit=top_k,\n",
    "    output_fields=[\"*\"]\n",
    ")\n",
    "\n",
    "print(len(documents))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "f6f2d276-e463-4db6-89f4-c12c0efccd53",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(insert count: 0, delete count: 1, upsert count: 0, timestamp: 444769705428254721, success count: 0, err count: 0)"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 删除数据\n",
    "collection.delete(expr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "12a8f117-19be-4336-b488-cb1af96314d8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0\n"
     ]
    }
   ],
   "source": [
    "# 再查一下，看看是否还存在\n",
    "documents = collection.query(\n",
    "    expr=expr,\n",
    "    offset=0,\n",
    "    limit=top_k,\n",
    "    output_fields=[\"*\"]\n",
    ")\n",
    "\n",
    "print(len(documents))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1084ba60-79ca-49d5-9705-8358bdccf7ae",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "78c55c9b-310c-4a0c-8844-fc1354853a6d",
   "metadata": {},
   "source": [
    "#### 知识点：向量数据库除了做对话场景，还能发挥什么能力"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "11f4cc71-c289-4bec-8f2a-94fd43f7daa0",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Embedding Use Case\n",
    "# https://platform.openai.com/docs/guides/embeddings/use-cases\n",
    "# 图片搜索、音频搜索、文本搜索、商品推荐..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "36bd0d4f-209c-43b2-abe5-9bc42d0e553b",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6b7a0b63-db59-4998-9cc8-46fdaf105ef8",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "c982d2ab-66be-4e8b-a661-02f79c07ab4c",
   "metadata": {},
   "source": [
    "### 第三架马车：Large Language Model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "bc2c2c6f-126f-4a38-90a9-fb2d3b544e3c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在上节课，我们说到了大模型在对话系统中，其实最关键的就是他的推理能力\n",
    "\n",
    "# 核心是如何选择一个大语言模型？"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "2971bf28-0582-4893-a489-4d2a2eda2f28",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Llama2 呼声很高，但是实际投产适用吗？\n",
    "# https://replicate.com/meta/llama-2-70b-chat\n",
    "# https://replicate.com/meta/llama-2-13b-chat\n",
    "# https://replicate.com/meta/llama-2-7b-chat\n",
    "\n",
    "# 我们来小试一下, 但发现好像中文场景下很糟糕"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "dd485654-d31a-40ae-94d4-ca0f30fe1f11",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "2dc243b7b21d4bc5a79a462e87babb7e",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Loading checkpoint shards:   0%|          | 0/2 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages/transformers/generation/configuration_utils.py:362: UserWarning: `do_sample` is set to `False`. However, `temperature` is set to `0.9` -- this flag is only used in sample-based generation modes. You should set `do_sample=True` or unset `temperature`. This was detected when initializing the generation config instance, which means the corresponding file may hold incorrect parameterization and should be fixed.\n",
      "  warnings.warn(\n",
      "/home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages/transformers/generation/configuration_utils.py:367: UserWarning: `do_sample` is set to `False`. However, `top_p` is set to `0.6` -- this flag is only used in sample-based generation modes. You should set `do_sample=True` or unset `top_p`. This was detected when initializing the generation config instance, which means the corresponding file may hold incorrect parameterization and should be fixed.\n",
      "  warnings.warn(\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "from transformers import AutoTokenizer, AutoModelForCausalLM\n",
    "\n",
    "model_path = './weights/language/llama2-chinese-chat-7b/'\n",
    "\n",
    "model = AutoModelForCausalLM.from_pretrained(\n",
    "    pretrained_model_name_or_path=model_path, \n",
    "    device_map='auto', \n",
    "    torch_dtype=torch.float16\n",
    ")\n",
    "model = model.eval()\n",
    "\n",
    "tokenizer = AutoTokenizer.from_pretrained(\n",
    "    pretrained_model_name_or_path=model_path, \n",
    "    use_fast=False)\n",
    "tokenizer.pad_token = tokenizer.eos_token"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "70f8498c-bc54-48df-83a3-71a224b4bd3a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
      "To disable this warning, you can either:\n",
      "\t- Avoid using `tokenizers` before the fork if possible\n",
      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
      "Fri Oct  6 23:58:19 2023       \n",
      "+---------------------------------------------------------------------------------------+\n",
      "| NVIDIA-SMI 535.98                 Driver Version: 535.98       CUDA Version: 12.2     |\n",
      "|-----------------------------------------+----------------------+----------------------+\n",
      "| GPU  Name                 Persistence-M | Bus-Id        Disp.A | Volatile Uncorr. ECC |\n",
      "| Fan  Temp   Perf          Pwr:Usage/Cap |         Memory-Usage | GPU-Util  Compute M. |\n",
      "|                                         |                      |               MIG M. |\n",
      "|=========================================+======================+======================|\n",
      "|   0  NVIDIA GeForce RTX 4090 ...    Off | 00000000:01:00.0 Off |                  N/A |\n",
      "| N/A   48C    P0              42W / 150W |  14099MiB / 16376MiB |      3%      Default |\n",
      "|                                         |                      |                  N/A |\n",
      "+-----------------------------------------+----------------------+----------------------+\n",
      "                                                                                         \n",
      "+---------------------------------------------------------------------------------------+\n",
      "| Processes:                                                                            |\n",
      "|  GPU   GI   CI        PID   Type   Process name                            GPU Memory |\n",
      "|        ID   ID                                                             Usage      |\n",
      "|=======================================================================================|\n",
      "|    0   N/A  N/A      2210      G   /usr/lib/xorg/Xorg                            4MiB |\n",
      "|    0   N/A  N/A      2512    C+G   ...libexec/gnome-remote-desktop-daemon      239MiB |\n",
      "|    0   N/A  N/A    701755      C   .../ml/anaconda3/envs/mobot/bin/python    13838MiB |\n",
      "+---------------------------------------------------------------------------------------+\n"
     ]
    }
   ],
   "source": [
    "# 来看看需要多少显存开销\n",
    "!nvidia-smi"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "6c3d52ae-43bd-4006-9c6f-b90f5167b7e9",
   "metadata": {},
   "outputs": [],
   "source": [
    "def Llama2Completion(prompt):\n",
    "    input_ids = tokenizer([prompt], return_tensors=\"pt\", add_special_tokens=False).input_ids.to('cuda')\n",
    "\n",
    "    generate_input = {\n",
    "        \"input_ids\": input_ids,\n",
    "        \"max_new_tokens\": 512,  # max is 4096\n",
    "        \"do_sample\": False,\n",
    "        \"top_p\": 1,\n",
    "        \"temperature\": 0.1,\n",
    "        \"repetition_penalty\": 1,\n",
    "        \"eos_token_id\": tokenizer.eos_token_id,\n",
    "        \"bos_token_id\": tokenizer.bos_token_id,\n",
    "        \"pad_token_id\": tokenizer.pad_token_id\n",
    "    }\n",
    "\n",
    "    generate_ids = model.generate(**generate_input)\n",
    "    return tokenizer.decode(generate_ids[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "3c39c779-0f34-47d2-b6c8-c1d33e9ad863",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages/transformers/generation/utils.py:1417: UserWarning: You have modified the pretrained model configuration to control generation. This is a deprecated strategy to control generation and will be removed soon, in a future version. Please use a generation configuration file (see https://huggingface.co/docs/transformers/main_classes/text_generation )\n",
      "  warnings.warn(\n",
      "/home/ml/anaconda3/envs/mobot/lib/python3.10/site-packages/transformers/generation/configuration_utils.py:362: UserWarning: `do_sample` is set to `False`. However, `temperature` is set to `0.1` -- this flag is only used in sample-based generation modes. You should set `do_sample=True` or unset `temperature`.\n",
      "  warnings.warn(\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "<s>Human: \n",
      "知识库：\n",
      "瑶瑶是谁? 瑶瑶是墨问西东的颜值担当，遥遥领先。\n",
      "小盖是谁? 小盖是墨问西东最帅的男人。\n",
      "池建强是谁？ 池老师是墨问西东的创始人兼CEO。\n",
      "\n",
      "请根据知识库推理出：小盖是什么\n",
      "</s>\n",
      "<s>Assistant:\n",
      "小盖是墨问西东最帅的男人。\n",
      "\n",
      "因此，小盖可能是一个男性，或者一个虚拟人物，被认为是墨问西东最帅的男人。\n",
      "</s>\n"
     ]
    }
   ],
   "source": [
    "# 试试最简单的问题\n",
    "\n",
    "prompt = \"\"\"\n",
    "<s>Human: \n",
    "知识库：\n",
    "瑶瑶是谁? 瑶瑶是墨问西东的颜值担当，遥遥领先。\n",
    "小盖是谁? 小盖是墨问西东最帅的男人。\n",
    "池建强是谁？ 池老师是墨问西东的创始人兼CEO。\n",
    "\n",
    "请根据知识库推理出：小盖是什么\n",
    "</s>\n",
    "<s>Assistant:\n",
    "\"\"\"\n",
    "\n",
    "print(Llama2Completion(prompt))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "id": "f009b835-9ea4-4e33-a20f-9b560cc7a892",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "<s>Human: \n",
      "知识库：\n",
      "瑶瑶是谁? 瑶瑶是墨问西东的颜值担当，遥遥领先。\n",
      "小盖是谁? 小盖是墨问西东最帅的男人。\n",
      "池建强是谁？ 池老师是墨问西东的创始人兼CEO。\n",
      "\n",
      "请根据知识库推理出：瑶瑶和小盖是什么关系\n",
      "</s>\n",
      "<s>Assistant:\n",
      "瑶瑶和小盖是墨问西东的员工。\n",
      "</s>\n"
     ]
    }
   ],
   "source": [
    "# 再试试有难度的\n",
    "prompt = \"\"\"\n",
    "<s>Human: \n",
    "知识库：\n",
    "瑶瑶是谁? 瑶瑶是墨问西东的颜值担当，遥遥领先。\n",
    "小盖是谁? 小盖是墨问西东最帅的男人。\n",
    "池建强是谁？ 池老师是墨问西东的创始人兼CEO。\n",
    "\n",
    "请根据知识库推理出：瑶瑶和小盖是什么关系\n",
    "</s>\n",
    "<s>Assistant:\n",
    "\"\"\"\n",
    "\n",
    "print(Llama2Completion(prompt))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "1bcac3ab-6fa6-4176-aadf-997324106022",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "<s>Human: 小盖是谁？</s>\n",
      "<s>Assistant: 小盖是墨问西东最帅的男人。</s>\n",
      "<s>Human: 瑶瑶是谁? </s>\n",
      "<s>Assistant: 瑶瑶是墨问西东的颜值担当，遥遥领先。</s>\n",
      "<s>Human: 那他俩是啥关系？</s>\n",
      "<s>Assistant: \n",
      "小盖和瑶瑶是墨问西东的两位主演，他们在剧中扮演了男女主角的角色。小盖扮演的是男主角，是一个勇敢、善良、热情的人，而瑶瑶则扮演的是女主角，是一个坚韧、勇敢、善良的人。两人在剧中展现了着深深的情感关系，彼此相爱，但却因为各自的缺陷和困难而受到困扰。最终，他们通过努力和尊重，才能找到了真正的爱情和幸福。\n",
      "\n",
      "总之，小盖和瑶瑶是墨问西东的两位主演，他们在剧中展现了着深深的情感关系，彼此相爱，但却因为各自的缺陷和困难而受到困扰。最终，他们通过努力和尊重，才能找到了真正的爱情和幸福。\n",
      "</s>\n"
     ]
    }
   ],
   "source": [
    "# 对于记忆力的场景\n",
    "prompt = \"\"\"\n",
    "<s>Human: 小盖是谁？</s>\n",
    "<s>Assistant: 小盖是墨问西东最帅的男人。</s>\n",
    "<s>Human: 瑶瑶是谁? </s>\n",
    "<s>Assistant: 瑶瑶是墨问西东的颜值担当，遥遥领先。</s>\n",
    "<s>Human: 那他俩是啥关系？</s>\n",
    "<s>Assistant: \n",
    "\"\"\"\n",
    "\n",
    "print(Llama2Completion(prompt))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "55460e80-20ab-4b83-a898-e84d067b63c5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 看起来对于 Llama2-Chinese-7B \n",
    "# 推理能力还是一个比较弱的能力\n",
    "\n",
    "# 那就试试更大参数量的模型（只有参数量超过一定的程度才会出现能力涌现）\n",
    "# 但我们显存有限，只有 16G，而且在推理时内存会不停上涨\n",
    "\n",
    "# 用量化的模型，就可以减少内存开销，例如 float16 -> int4\n",
    "# 4 位量化 LLaMA 模型的 GPU 要求：\n",
    "# LLaMA Model\tMinimum VRAM Requirement\tRecommended GPU Examples\n",
    "# LLaMA-7B\t6GB\t    RTX 3060, GTX 1660, 2060, AMD 5700 XT, RTX 3050\n",
    "# LLaMA-13B\t10GB\tAMD 6900 XT, RTX 2060 12GB, 3060 12GB, 3080, A2000\n",
    "# LLaMA-30B\t20GB\tRTX 3080 20GB, A4500, A5000, 3090, 4090, 6000, Tesla V100, Tesla P40\n",
    "# LLaMA-65B\t40GB\tA100 40GB, 2x3090, 2x4090, A40, RTX A6000, A8000"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "6954f0d9-bdbb-4a42-b28a-5061c1cc958a",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Warning: please make sure that you are using the latest codes and checkpoints, especially if you used Qwen-7B before 09.25.2023.请使用最新模型和代码，尤其如果你在9月25日前已经开始使用Qwen-7B，千万注意不要使用错误代码和模型。\n",
      "Try importing flash-attention for faster inference...\n",
      "Warning: import flash_attn rotary fail, please install FlashAttention rotary to get higher efficiency https://github.com/Dao-AILab/flash-attention/tree/main/csrc/rotary\n",
      "Warning: import flash_attn rms_norm fail, please install FlashAttention layer_norm to get higher efficiency https://github.com/Dao-AILab/flash-attention/tree/main/csrc/layer_norm\n",
      "Warning: import flash_attn fail, please install FlashAttention to get higher efficiency https://github.com/Dao-AILab/flash-attention\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "c9968f0b955e4832883131aa6fd0e1bb",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Loading checkpoint shards:   0%|          | 0/5 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import torch\n",
    "from transformers import AutoTokenizer, AutoModelForCausalLM\n",
    "\n",
    "model_path = './weights/language/qwen-14b-chat-int4/'\n",
    "\n",
    "model = AutoModelForCausalLM.from_pretrained(\n",
    "    pretrained_model_name_or_path=model_path, \n",
    "    device_map='auto', \n",
    "    trust_remote_code=True\n",
    ").eval()\n",
    "\n",
    "tokenizer = AutoTokenizer.from_pretrained(\n",
    "    pretrained_model_name_or_path=model_path, \n",
    "    trust_remote_code=True\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "75aa64c8-e8fd-4cd6-bc85-e395ab2ff6d0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Sat Oct  7 00:00:00 2023       \n",
      "+---------------------------------------------------------------------------------------+\n",
      "| NVIDIA-SMI 535.98                 Driver Version: 535.98       CUDA Version: 12.2     |\n",
      "|-----------------------------------------+----------------------+----------------------+\n",
      "| GPU  Name                 Persistence-M | Bus-Id        Disp.A | Volatile Uncorr. ECC |\n",
      "| Fan  Temp   Perf          Pwr:Usage/Cap |         Memory-Usage | GPU-Util  Compute M. |\n",
      "|                                         |                      |               MIG M. |\n",
      "|=========================================+======================+======================|\n",
      "|   0  NVIDIA GeForce RTX 4090 ...    Off | 00000000:01:00.0 Off |                  N/A |\n",
      "| N/A   50C    P0              36W / 150W |  10095MiB / 16376MiB |      3%      Default |\n",
      "|                                         |                      |                  N/A |\n",
      "+-----------------------------------------+----------------------+----------------------+\n",
      "                                                                                         \n",
      "+---------------------------------------------------------------------------------------+\n",
      "| Processes:                                                                            |\n",
      "|  GPU   GI   CI        PID   Type   Process name                            GPU Memory |\n",
      "|        ID   ID                                                             Usage      |\n",
      "|=======================================================================================|\n",
      "|    0   N/A  N/A      2210      G   /usr/lib/xorg/Xorg                            4MiB |\n",
      "|    0   N/A  N/A      2512    C+G   ...libexec/gnome-remote-desktop-daemon      239MiB |\n",
      "|    0   N/A  N/A    703483      C   .../ml/anaconda3/envs/mobot/bin/python     9834MiB |\n",
      "+---------------------------------------------------------------------------------------+\n"
     ]
    }
   ],
   "source": [
    "# 来看看需要多少显存开销\n",
    "!nvidia-smi"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "7f055daf-8baa-4101-b627-fbccdbc5f024",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "小盖是墨问西东最帅的男人。\n"
     ]
    }
   ],
   "source": [
    "# 来试试 13B 参数量的模型，能力如何？\n",
    "\n",
    "# 试试最简单的问题\n",
    "\n",
    "prompt = \"\"\"\n",
    "知识库：\n",
    "瑶瑶是谁? 瑶瑶是墨问西东的颜值担当，遥遥领先。\n",
    "小盖是谁? 小盖是墨问西东最帅的男人。\n",
    "池建强是谁？ 池老师是墨问西东的创始人兼CEO。\n",
    "\n",
    "请根据知识库回答：小盖是什么\n",
    "\"\"\"\n",
    "\n",
    "response, history = model.chat(tokenizer, prompt, history=None)\n",
    "print(response)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "00048eac-c9c7-42fe-87d8-38d811662e31",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "根据知识库，瑶瑶是墨问西东的颜值担当，遥遥领先；小盖是墨问西东最帅的男人。因此，瑶瑶和小盖都是墨问西东的人，但具体关系未知。\n"
     ]
    }
   ],
   "source": [
    "# 再试试有难度的\n",
    "prompt = \"\"\"\n",
    "知识库：\n",
    "瑶瑶是谁? 瑶瑶是墨问西东的颜值担当，遥遥领先。\n",
    "小盖是谁? 小盖是墨问西东最帅的男人。\n",
    "池建强是谁？ 池老师是墨问西东的创始人兼CEO。\n",
    "\n",
    "请根据知识库回答：瑶瑶和小盖是什么关系\n",
    "\"\"\"\n",
    "\n",
    "response, history = model.chat(tokenizer, prompt, history=None)\n",
    "print(response)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "b61826d0-1389-4876-90bf-34b5ceba81b7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "小盖和瑶瑶是同事关系，同在墨问西东团队中担任自然语言处理相关的研发工作。\n"
     ]
    }
   ],
   "source": [
    "# 对于记忆力的场景\n",
    "prompt = \"\"\"\n",
    "Human: 小盖是谁？\n",
    "Assistant: 小盖是墨问西东最帅的男人。\n",
    "Human: 瑶瑶是谁? \n",
    "Assistant: 瑶瑶是墨问西东的颜值担当，遥遥领先。\n",
    "Human: 那他俩是啥关系？\n",
    "Assistant:\n",
    "\"\"\"\n",
    "\n",
    "response, history = model.chat(tokenizer, prompt, history=None)\n",
    "print(response)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "78dfa983-30f5-4dd1-8344-d4842db625d5",
   "metadata": {},
   "source": [
    "#### 模型选择技巧"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "396aa0b6-0381-47d3-b5ab-141c5ab19234",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 7B参数量的模型 float16 需要14G显存！\n",
    "\n",
    "# 13B参数量的模型 int4 量化 显存只需要9G显存，开销更低！性能表现更好\n",
    "\n",
    "# 工程上选择更大参数量的模型去做量化！"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cbb58fb4-20f2-4380-ad5a-47b39189173e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# LLMs Leader Board\n",
    "# https://opencompass.org.cn/leaderboard-llm"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2c72f6f0-b731-4000-9cb7-1d5011bc27cb",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ad36d4c2-1492-4fa5-bbf6-560a1675fad1",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "730167ae-21ce-419e-8bfe-6e3cb7b1ceb7",
   "metadata": {},
   "source": [
    "### 总结和回顾"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26b1e2f1-a6a2-4234-9cea-7a22477c04f3",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fd697625-9f95-4a0f-bee4-b52825da3ba6",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f90b9a8a-9e44-444c-8a68-48fd2c6fc51c",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ea17a037-8969-46ad-b5ef-4ba57119cfcf",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
